diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..bacf370669bc10371bae05b1201b9e1a8eb83d91 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,14 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +chatbot/assets/ai_lab_recipes_logo.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/chatbot_ui.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/codegen_ui.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/image_analysis.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/install_continue_extension.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/object_detection.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/rag_ui.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/summarizer_ui.png filter=lfs diff=lfs merge=lfs -text +chatbot/assets/whisper.png filter=lfs diff=lfs merge=lfs -text +chatbot/data/jfk.wav filter=lfs diff=lfs merge=lfs -text +chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/images/chatbot-architecture.png filter=lfs diff=lfs merge=lfs -text diff --git a/chatbot/.devcontainer/Containerfile b/chatbot/.devcontainer/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..ff25ae55ce3587be9d3646c74b8f1364813d578a --- /dev/null +++ b/chatbot/.devcontainer/Containerfile @@ -0,0 +1,9 @@ +FROM quay.io/containers/podman:v5.0.2 + +USER root + +COPY requirements-test.txt . + +RUN dnf install -y python3.11 python3-pip buildah git make && \ + dnf clean all && \ + pip3 install -r requirements-test.txt diff --git a/chatbot/.devcontainer/devcontainer.json b/chatbot/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000000000000000000000000000000..02bb0e8009529e06ac1d16aac07d544b464b99bb --- /dev/null +++ b/chatbot/.devcontainer/devcontainer.json @@ -0,0 +1,12 @@ +{ + "name": "recipes", + "build": { + "dockerfile": "Containerfile", + "context": ".." + }, + "privileged": true, + "containerEnv": { + "REGISTRY": "ghcr.io", + "IMAGE_NAME": "ai-lab-recipes/playground" + } +} diff --git a/chatbot/.gitattributes b/chatbot/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..94f480de94e1d767531580401cbf13844868e82b --- /dev/null +++ b/chatbot/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/chatbot/.github/workflows/README.md b/chatbot/.github/workflows/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9afaa112717cd86c23bf974924cfdc8c61d27ae9 --- /dev/null +++ b/chatbot/.github/workflows/README.md @@ -0,0 +1,33 @@ +# AI-Lab Recipes Infrastructure Documentation + +## Standard Wofklows + +Our standard workflows deal with building components and pushing their images to `quay.io/ai-lab`. These components include: + - recipe applications: + - Chatbot + - Codegen + - Summarizer + - RAG + - model_servers + - models + - instructlab workflows + - training bootc workflows + +For a full list of the images we build check out or [quay organization](https://quay.io/organization/ai-lab). These standard workflows should all be run against our standard repo `containers/ai-labs-recipes` rather than the mirror repo. + +## Testing frameworks + +Our testing frameworks are a bit different from our standard workflows. In terms of compute, some of these jobs run either AWS machines provisioned via terraform using secrets in the github repository, or customized github hosted action runners, as well as the standard ubuntu-24.04 github runners for jobs not requiring additional resources. + +These workflows start by checking out the [terraform-test-environment-module](https://github.com/containers/terraform-test-environment-module) repo, as well as the code in `containers/ai-lab-recipes` at the `main` branch. Then it will provision the terraform instance, install the correct ansible playbook requirements, and runs a coressponding playbook. Aditional actions may also be taken depending on the testing framework in question. + +Finally all of our testing framework workflows will call `terraform destroy` to remove the aws instance we have provisioned and publish the results of the workflow to slack. + +IMPORTATNT: If you are doing development and testing, please make sure that instances in AWS are spun down before leaving if you have access to the AWS account. + +### training-e2e + +The test environment is initially based off of `Fedroa 40`. +It bootstraps a `g5.8xlarge` AWS EC2 instance with Terraform. +Provisioning is executed with ansible. The ansible playbook is invoking bootc install and +reboots the instance. diff --git a/chatbot/.github/workflows/chatbot.yaml b/chatbot/.github/workflows/chatbot.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b188c06e4f8d894083069e809eac13c7abc577e0 --- /dev/null +++ b/chatbot/.github/workflows/chatbot.yaml @@ -0,0 +1,149 @@ +name: chatbot + +on: + pull_request: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/chatbot/** + - .github/workflows/chatbot.yaml + - ./recipes/natural_language_processing/chatbot/app/** + - ./renovate.json + push: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/chatbot/** + - .github/workflows/chatbot.yaml + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: chatbot + +jobs: + chatbot-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - uses: actions/checkout@v4.1.7 + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install opentelemetry dependencies + run: | + pip install --no-cache-dir opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation + + - name: Download OpenTelemetry Collector Contrib + run: | + wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.103.0/otelcol-contrib_0.103.0_linux_amd64.tar.gz + tar -xvf otelcol-contrib_0.103.0_linux_amd64.tar.gz + + - name: Write secrets to files + run: | + echo "${{ secrets.ROSA_OTEL_CACERT }}" > /tmp/ca.crt + echo "${{ secrets.ROSA_OTEL_SERVER_CRT }}" > /tmp/server.crt + echo "${{ secrets.ROSA_OTEL_SERVER_KEY }}" > /tmp/server.key + + - name: Configure OpenTelemetry Collector + run: | + echo ' + receivers: + otlp: + protocols: + grpc: + http: + exporters: + otlphttp: + endpoint: "${{ secrets.ROSA_OTEL_ENDPOINT }}" + tls: + insecure: false + cert_file: /tmp/server.crt + key_file: /tmp/server.key + ca_file: /tmp/ca.crt + debug: + verbosity: detailed + service: + pipelines: + traces: + receivers: [otlp] + exporters: [debug, otlphttp] + ' > otel-collector-config.yaml + + - name: Run OpenTelemetry Collector + run: | + ./otelcol-contrib --config otel-collector-config.yaml > otel-collector.log 2>&1 & + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Start job trace + run: | + export WORKFLOW_NAME="chatbot" + export JOB_NAME="chatbot-build-and-push" + export TRACE_ACTION="start" + python ci/trace-steps.py + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/containers/${{ env.IMAGE_NAME }} + tags: latest + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app/Containerfile + context: recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app + + - name: Install Dependencies + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + run: make install + + - name: Download model + working-directory: ./models + run: make download-model-granite + + - name: Run Functional Tests + shell: bash + run: make functional-tests + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + + - name: Login to Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Image + id: push_image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} + + - name: End job trace + run: | + export WORKFLOW_NAME="chatbot" + export JOB_NAME="chatbot-build-and-push" + export TRACE_ACTION="end" + python ci/trace-steps.py + diff --git a/chatbot/.github/workflows/codegen.yaml b/chatbot/.github/workflows/codegen.yaml new file mode 100644 index 0000000000000000000000000000000000000000..80377b425e499289f0aa2eb6d0d54d4e10727539 --- /dev/null +++ b/chatbot/.github/workflows/codegen.yaml @@ -0,0 +1,88 @@ +name: codegen + +on: + pull_request: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/codegen/** + - .github/workflows/codegen.yaml + push: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/codegen/** + - .github/workflows/codegen.yaml + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: codegen + +jobs: + codegen-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/containers/${{ env.IMAGE_NAME }} + tags: latest + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app/Containerfile + context: recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install Dependencies + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + run: make install + + - name: Download model + working-directory: ./models + run: make download-model-mistral-code + + - name: Run Functional Tests + shell: bash + run: make functional-tests + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + + - name: Login to Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Image + id: push_image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/instructlab.yaml b/chatbot/.github/workflows/instructlab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..485a3f9bf1e273ff2142e7c56e36877ae5b53532 --- /dev/null +++ b/chatbot/.github/workflows/instructlab.yaml @@ -0,0 +1,79 @@ +name: Instructlab image builds + +on: + schedule: # schedule the job to run at 12 AM daily + - cron: '0 0 * * *' + + # pull_request: + # branches: + # - main + # paths: + # - .github/workflows/instructlab_baseimages_build_push.yaml + # - training/nvidia + # push: + # branches: + # - main + # paths: + # - .github/workflows/instructlab_baseimages_build_push.yaml + # - training/nvidia + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + REGISTRY: ghcr.io + REGISTRY_ORG: containers + +jobs: + instructlab-nvidia: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests') && github.repository == 'containers-mirror/ai-lab-recipes'" + strategy: + matrix: + include: + - make_target: nvidia + - make_target: amd + runs-on: ubuntu-latest + # runs-on: ubuntu-22.04-2core # starting with minimal option + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.7 + + - name: Build Image + id: build_image + run: make ${{ matrix.make_target}} + working-directory: ./training/instructlab + + - name: Login to Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/chatbot/.github/workflows/manual_build_trigger.yaml b/chatbot/.github/workflows/manual_build_trigger.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1b899f39f3411c12077f264e47c6a778828d6af3 --- /dev/null +++ b/chatbot/.github/workflows/manual_build_trigger.yaml @@ -0,0 +1,343 @@ +name: Update quay.io/ai-lab images with manual trigger + +on: + workflow_dispatch: + +env: + REGISTRY: quay.io/ai-lab + CHATBOT_IMAGE_NAME: chatbot + CODEGEN_IMAGE_NAME: codegen + SUMMARIZER_IMAGE_NAME: summarizer + LLAMACPP_PYTHON_IMAGE_NAME: llamacpp_python + LLAMACPP_PYTHON_VULKAN_IMAGE_NAME: llamacpp_python_vulkan + LLAMACPP_PYTHON_CUDA_IMAGE_NAME: llamacpp_python_cuda + CHROMADB_IMAGE_NAME: chromadb + RAG_IMAGE_NAME: rag + +jobs: + build-and-push-llamacpp-python-cuda-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - uses: actions/checkout@v4.1.7 + + - name: Build llamacpp_python cuda + id: build_llamacpp_python_cuda + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.LLAMACPP_PYTHON_CUDA_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64 + containerfiles: ./model_servers/llamacpp_python/cuda/Containerfile + context: model_servers/llamacpp_python + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push llamacpp_python cuda image + id: push_llamacpp_python_cuda + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_llamacpp_python_cuda.outputs.image }} + tags: ${{ steps.build_llamacpp_python_cuda.outputs.tags }} + + build-and-push-llamacpp-python-vulkan-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - uses: actions/checkout@v4.1.7 + + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build llamacpp_python vulkan + id: build_llamacpp_python_vulkan + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.LLAMACPP_PYTHON_VULKAN_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/arm64 + containerfiles: ./model_servers/llamacpp_python/vulkan/Containerfile + context: model_servers/llamacpp_python + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push llamacpp_python vulkan image + id: push_llamacpp_python_vulkan + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_llamacpp_python_vulkan.outputs.image }} + tags: ${{ steps.build_llamacpp_python_vulkan.outputs.tags }} + + build-and-push-llamacpp-python-base-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - uses: actions/checkout@v4.1.7 + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build llamacpp_python base + id: build_llamacpp_python_base + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.LLAMACPP_PYTHON_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + containerfiles: ./model_servers/llamacpp_python/base/Containerfile + context: model_servers/llamacpp_python + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push llamacpp_python image + id: push_llamacpp_python_base + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_llamacpp_python_base.outputs.image }} + tags: ${{ steps.build_llamacpp_python_base.outputs.tags }} + + build-and-push-rag-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - uses: actions/checkout@v4.1.7 + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build rag + id: build_rag + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.RAG_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/rag/app/Containerfile + context: recipes/natural_language_processing/rag/app + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push rag image + id: push_rag + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_rag.outputs.image }} + tags: ${{ steps.build_rag.outputs.tags }} + + build-and-push-chromadb-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4.1.7 + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build chromadb + id: build_chromadb + uses: redhat-actions/buildah-build@v2 + with: + image: ${{ env.CHROMADB_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + context: vector_dbs/chromadb + containerfiles: ./vector_dbs/chromadb/Containerfile + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push chromadb image + id: push_chromadb + uses: redhat-actions/push-to-registry@v2 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_chromadb.outputs.image }} + tags: ${{ steps.build_chromadb.outputs.tags }} + + build-and-push-codegen-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4.1.7 + + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build codegen image + id: build_codegen_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.CODEGEN_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/codegen/app/Containerfile + context: recipes/natural_language_processing/codegen/app + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push codegen image + id: push_codegen_image + uses: redhat-actions/push-to-registry@v2.8 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_codegen_image.outputs.image }} + tags: ${{ steps.build_codegen_image.outputs.tags }} + + build-and-push-chatbot-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4.1.7 + + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build chatbot image + id: build_chatbot_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.CHATBOT_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/chatbot/app/Containerfile + context: recipes/natural_language_processing/chatbot/app + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push chatbot image + id: push_chatbot_image + uses: redhat-actions/push-to-registry@v2.8 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_chatbot_image.outputs.image }} + tags: ${{ steps.build_chatbot_image.outputs.tags }} + + build-and-push-summarizer-image: + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v4.1.7 + + # required for multi-arch builds + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build summarizer image + id: build_summarizer_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.SUMMARIZER_IMAGE_NAME }} + tags: latest ${{ github.sha }} + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/summarizer/app/Containerfile + context: recipes/natural_language_processing/summarizer/app + + - name: Login to quay.io + uses: redhat-actions/podman-login@v1 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push summarizer image + id: push_summarizer_image + uses: redhat-actions/push-to-registry@v2.8 + with: + registry: ${{ env.REGISTRY }} + image: ${{ steps.build_summarizer_image.outputs.image }} + tags: ${{ steps.build_summarizer_image.outputs.tags }} diff --git a/chatbot/.github/workflows/mirror_repository.yaml b/chatbot/.github/workflows/mirror_repository.yaml new file mode 100644 index 0000000000000000000000000000000000000000..1bc8b06412d274b100b21208c99e348f1cc24bba --- /dev/null +++ b/chatbot/.github/workflows/mirror_repository.yaml @@ -0,0 +1,40 @@ +name: Mirror Repository + +on: + push: + branches: + - main + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + mirror-repository: + if: github.repository == 'containers/ai-lab-recipes' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4.1.7 + with: + fetch-depth: 0 + + - uses: pixta-dev/repository-mirroring-action@v1.1.1 + with: + target_repo_url: + git@github.com:containers-mirror/ai-lab-recipes.git + ssh_private_key: + ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/chatbot/.github/workflows/model_converter.yaml b/chatbot/.github/workflows/model_converter.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f236dbe77b8b9e3e82fadb5e1ceafaa848746730 --- /dev/null +++ b/chatbot/.github/workflows/model_converter.yaml @@ -0,0 +1,73 @@ +name: Update quay.io/ai-lab model images + +on: + pull_request: + branches: + - main + paths: + - 'convert_models/**' + - '!convert_models/README.md' + - '.github/workflows/model_converter.yaml' + push: + branches: + - main + paths: + - 'convert_models/**' + - '!convert_models/README.md' + - '.github/workflows/model_converter.yaml' + + workflow_dispatch: + +env: + REGISTRY: quay.io + REGISTRY_ORG: ai-lab + +jobs: + model-converter-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + strategy: + matrix: + include: + - platforms: linux/amd64,linux/arm64 + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY_ORG }}/model-converter + platforms: ${{ matrix.platforms }} + tags: latest + containerfiles: convert_models/Containerfile + context: convert_models + + - name: Login to Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/model_servers.yaml b/chatbot/.github/workflows/model_servers.yaml new file mode 100644 index 0000000000000000000000000000000000000000..89961a39e49bd0173a12b98694d6c5c34ad66f24 --- /dev/null +++ b/chatbot/.github/workflows/model_servers.yaml @@ -0,0 +1,133 @@ +name: Model Servers + +on: + pull_request: + branches: + - main + paths: + - 'model_servers/**' + - 'models/Makefile' + - .github/workflows/model_servers.yaml + push: + branches: + - main + paths: + - 'model_servers/**' + - 'models/Makefile' + - .github/workflows/model_servers.yaml + tags: + - '*' + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + REGISTRY_ORG: containers + +jobs: + model-servers-buid-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + strategy: + matrix: + include: + - image_name: llamacpp_python + model: granite + flavor: base + directory: llamacpp_python + platforms: linux/amd64,linux/arm64 + no_gpu: 1 + - image_name: llamacpp-python-cuda + model: granite + flavor: cuda + directory: llamacpp_python + platforms: linux/amd64,linux/arm64 + no_gpu: 0 + - image_name: llamacpp-python-vulkan-amd + model: granite + flavor: vulkan/amd64 + directory: llamacpp_python + platforms: linux/amd64 + no_gpu: 0 + - image_name: llamacpp-python-vulkan-arm + model: granite + flavor: vulkan/arm64 + directory: llamacpp_python + platforms: linux/arm64 + no_gpu: 0 + - image_name: whispercpp + model: whisper-small + flavor: base + directory: whispercpp + platforms: linux/amd64,linux/arm64 + no_gpu: 1 + - image_name: object_detection_python + model: facebook-detr-resnet-101 + flavor: base + directory: object_detection_python + platforms: linux/amd64,linux/arm64 + no_gpu: 1 + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/${{ github.repository_owner}}/${{ matrix.image_name }} + platforms: ${{ matrix.platforms }} + tags: latest ${{ github.ref_type == 'tag' && github.ref_name || '' }} + containerfiles: ./model_servers/${{ matrix.directory }}/${{ matrix.flavor }}/Containerfile + context: model_servers/${{ matrix.directory }}/ + + - name: Download model + working-directory: ./models + run: make download-model-${{ matrix.model }} + + - name: Install python dependencies + working-directory: ./model_servers/${{ matrix.directory }}/ + run: make install + + - name: Run non-gpu tests + working-directory: ./model_servers/${{ matrix.directory }}/ + if: ${{ matrix.no_gpu }} + run: make test REGISTRY=${{ env.REGISTRY }} IMAGE_NAME=${{ env.REGISTRY_ORG }}/${{ matrix.image_name}}:latest + + - name: Login to Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/models.yaml b/chatbot/.github/workflows/models.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7a32db46351b06bd4160701b352cc7a8d55bfa0e --- /dev/null +++ b/chatbot/.github/workflows/models.yaml @@ -0,0 +1,94 @@ +name: Update quay.io/ai-lab model images + +on: + schedule: # schedule the job to run at 12 AM daily + - cron: '0 0 * * *' + + pull_request: + branches: + - main + paths: + - .github/workflows/model_image_build_push.yaml + push: + branches: + - main + paths: + - .github/workflows/model_image_build_push.yaml + + workflow_dispatch: + +env: + REGISTRY: quay.io + REGISTRY_ORG: ai-lab + +jobs: + models-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + strategy: + matrix: + include: + - image_name: mistral-7b-code-16k-qlora + label: Q4_K_M + url: https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF/resolve/main/mistral-7b-code-16k-qlora.Q4_K_M.gguf + platforms: linux/amd64,linux/arm64 + - image_name: mistral-7b-instruct + label: Q4_K_M + url: https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf + platforms: linux/amd64,linux/arm64 + - image_name: merlinite-7b-lab + label: Q4_K_M + url: https://huggingface.co/instructlab/merlinite-7b-lab-GGUF/resolve/main/merlinite-7b-lab-Q4_K_M.gguf + platforms: linux/amd64,linux/arm64 + - image_name: granite-7b-lab + label: Q4_K_M + url: https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf + platforms: linux/amd64,linux/arm64 + - image_name: whisper-small + url: https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin + platforms: linux/amd64,linux/arm64 + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY_ORG }}/${{ matrix.image_name }} + platforms: ${{ matrix.platforms }} + labels: | + ${{ matrix.label }} + build-args: | + MODEL_URL=${{ matrix.url }} + tags: latest + containerfiles: ./models/Containerfile + context: models + + - name: Login to Container Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/object_detection.yaml b/chatbot/.github/workflows/object_detection.yaml new file mode 100644 index 0000000000000000000000000000000000000000..7c06047386a8ac82b4700e277d30928c3e8fb82a --- /dev/null +++ b/chatbot/.github/workflows/object_detection.yaml @@ -0,0 +1,89 @@ +name: Object Detection + +on: + pull_request: + branches: + - main + paths: + - ./recipes/computer_vision/object_detection/** + - .github/workflows/object_detection.yaml + push: + branches: + - main + paths: + - ./recipes/computer_vision/object_detection/** + - .github/workflows/object_detection.yaml + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + REGISTRY_ORG: containers + RECIPE_NAME: object_detection + RECIPE_TYPE: computer_vision + IMAGE_NAME: object_detection_client + +jobs: + object-detection-client-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/${{ env.REGISTRY_ORG }}/${{ env.IMAGE_NAME }} + tags: latest + platforms: linux/amd64,linux/arm64 + containerfiles: ./recipes/${{ env.RECIPE_TYPE }}/${{ env.RECIPE_NAME }}/app/Containerfile + context: recipes/${{ env.RECIPE_TYPE }}/${{ env.RECIPE_NAME }}/app + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install Dependencies + working-directory: ./recipes/${{ env.RECIPE_TYPE }}/${{ env.RECIPE_NAME }} + run: make install + + - name: Download model + working-directory: ./models + run: make download-model-facebook-detr-resnet-101 + + - name: Run Functional Tests + shell: bash + run: make functional-tests + working-directory: ./recipes/${{ env.RECIPE_TYPE }}/${{ env.RECIPE_NAME }} + + - name: Login to Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Image + id: push_image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/rag.yaml b/chatbot/.github/workflows/rag.yaml new file mode 100644 index 0000000000000000000000000000000000000000..af827fdc0e82be231c27fafce48cfbd9b89814b5 --- /dev/null +++ b/chatbot/.github/workflows/rag.yaml @@ -0,0 +1,93 @@ +name: rag + +on: + pull_request: + branches: + - main + paths: + - 'recipes/common/Makefile.common' + - 'recipes/natural_language_processing/rag/**' + - '.github/workflows/rag.yaml' + push: + branches: + - main + paths: + - 'recipes/common/Makefile.common' + - 'recipes/natural_language_processing/rag/**' + - '.github/workflows/rag.yaml' + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: rag + +jobs: + rag-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - name: Remove unnecessary files + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/containers/${{ env.IMAGE_NAME }} + tags: latest + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app/Containerfile + context: recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install Dependencies + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + run: make install + + - name: Download model + working-directory: ./models + run: make download-model-granite + + - name: Run Functional Tests + shell: bash + run: make functional-tests + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + + - name: Login to Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Image + id: push_image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/summarizer.yaml b/chatbot/.github/workflows/summarizer.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5f5b48f9d282fd7644bee5b31f29059ceab5cfde --- /dev/null +++ b/chatbot/.github/workflows/summarizer.yaml @@ -0,0 +1,88 @@ +name: summarizer + +on: + pull_request: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/summarizer/** + - .github/workflows/summarizer.yaml + push: + branches: + - main + paths: + - ./recipes/common/Makefile.common + - ./recipes/natural_language_processing/summarizer/** + - .github/workflows/summarizer.yaml + + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: summarizer + +jobs: + summarizer-build-and-push: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + services: + registry: + image: registry:2.8.3 + ports: + - 5000:5000 + steps: + - uses: actions/checkout@v4.1.7 + + - name: Install qemu dependency + run: | + sudo apt-get update + sudo apt-get install -y qemu-user-static + + - name: Build Image + id: build_image + uses: redhat-actions/buildah-build@v2.13 + with: + image: ${{ env.REGISTRY }}/containers/${{ env.IMAGE_NAME }} + tags: latest + platforms: linux/amd64, linux/arm64 + containerfiles: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app/Containerfile + context: recipes/natural_language_processing/${{ env.IMAGE_NAME }}/app + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install Dependencies + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + run: make install + + - name: Download model + working-directory: ./models + run: make download-model-granite + + - name: Run Functional Tests + shell: bash + run: make functional-tests + working-directory: ./recipes/natural_language_processing/${{ env.IMAGE_NAME }} + + - name: Login to Registry + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/podman-login@v1.7 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push Image + id: push_image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ steps.build_image.outputs.image }} + tags: ${{ steps.build_image.outputs.tags }} + registry: ${{ env.REGISTRY }} diff --git a/chatbot/.github/workflows/test-trace-steps.yaml b/chatbot/.github/workflows/test-trace-steps.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5fb1cca66fb60a19fd48ad69a9b78bff6307304d --- /dev/null +++ b/chatbot/.github/workflows/test-trace-steps.yaml @@ -0,0 +1,94 @@ +# To run locally +# act -W .github/workflows/test-trace-steps.yaml --container-architecture linux/amd64 -b ci/logs:/logs + +name: Test Workflow + +on: + pull_request: + branches: + - main + paths: + - .github/workflows/test-trace-steps.yaml + workflow_dispatch: + +jobs: + test-build: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4.1.7 + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: | + pip install --no-cache-dir opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation + + - name: Download OpenTelemetry Collector Contrib + run: | + wget https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.103.0/otelcol-contrib_0.103.0_linux_amd64.tar.gz + tar -xvf otelcol-contrib_0.103.0_linux_amd64.tar.gz + + - name: Write secrets to files + run: | + echo "${{ secrets.ROSA_OTEL_CACERT }}" > /tmp/ca.crt + echo "${{ secrets.ROSA_OTEL_SERVER_CRT }}" > /tmp/server.crt + echo "${{ secrets.ROSA_OTEL_SERVER_KEY }}" > /tmp/server.key + + - name: Configure OpenTelemetry Collector + run: | + echo ' + receivers: + otlp: + protocols: + grpc: + http: + exporters: + otlphttp: + endpoint: "${{ secrets.ROSA_OTEL_ENDPOINT }}" + tls: + insecure: false + cert_file: /tmp/server.crt + key_file: /tmp/server.key + ca_file: /tmp/ca.crt + debug: + verbosity: detailed + service: + pipelines: + traces: + receivers: [otlp] + exporters: [debug, otlphttp] + ' > otel-collector-config.yaml + + - name: Run OpenTelemetry Collector + run: | + ./otelcol-contrib --config otel-collector-config.yaml > otel-collector.log 2>&1 & + + - name: Start job trace + run: | + export WORKFLOW_NAME="test-trace" + export JOB_NAME="test-build" + export TRACE_ACTION="start" + python ci/trace-steps.py + + - name: Build + run: | + echo "Simulating build step..." + sleep 2 + + - name: Test + run: | + echo "Simulating test step..." + sleep 2 + + - name: End job trace + run: | + export WORKFLOW_NAME="test-trace" + export JOB_NAME="test-build" + export TRACE_ACTION="end" + python ci/trace-steps.py + + - name: Display OpenTelemetry Collector Logs + run: cat otel-collector.log diff --git a/chatbot/.github/workflows/testing_framework.yaml b/chatbot/.github/workflows/testing_framework.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a689ea17f90cae6a448e9128fba767ee8a0a92a2 --- /dev/null +++ b/chatbot/.github/workflows/testing_framework.yaml @@ -0,0 +1,195 @@ +name: Testing Framework + +on: + schedule: # schedule the job to run once a day + - cron: '0 0 * * *' + + #workflow_dispatch: + + # pull_request: ## temporary for debugging development purposes + # branches: + # - main + +env: + TF_VAR_aws_region: "eu-west-2" + TF_VAR_aws_ami_owners: '["125523088429"]' + TF_VAR_aws_ami_name: '["Fedora-Cloud-Base-39*"]' + TF_VAR_aws_volume_size: 100 + TF_VAR_aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + TF_VAR_aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # AWS_DEFAULT_REGION: "eu-west-2" + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + integration-tests: + if: github.repository == 'containers/ai-lab-recipes' + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + aws_image_type: t3a.medium + aws_ami_architecture: x86_64 + - arch: arm64 + aws_image_type: m7g.medium + aws_ami_architecture: arm64 + - arch: amd64 # gpu enabled + aws_image_type: g4dn.xlarge + aws_ami_architecture: x86_64 + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + with: + ref: 'main' + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: Checkout terraform module + id: checkout-module + uses: actions/checkout@v4.1.7 + with: + repository: containers/terraform-test-environment-module + path: terraform-test-environment-module + ref: 'main' + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3.1.2 + with: + terraform_version: "1.7.5" + terraform_wrapper: false + + - name: Init + run: terraform init + working-directory: terraform-test-environment-module + + - name: Bootstrap + id: up + run: terraform apply -auto-approve -lock=false + working-directory: terraform-test-environment-module + env: + TF_VAR_aws_instance_type: ${{ matrix.aws_image_type }} + TF_VAR_aws_ami_architecture: ${{ matrix.aws_ami_architecture }} + + - name: Terraform Output + id: terraform-output + run: | + echo "id=$(terraform output id | xargs)" >> $GITHUB_OUTPUT + echo "url=$(terraform output host | xargs)" >> $GITHUB_OUTPUT + echo "pem_filename=$(terraform output pem_filename | xargs)" >> $GITHUB_OUTPUT + working-directory: terraform-test-environment-module + + - name: Ansible Collections + run: ansible-galaxy install -r ./provision/requirements.yml + working-directory: ./recipes/natural_language_processing/chatbot + + - name: Provision + run: | + ansible-playbook ./recipes/natural_language_processing/chatbot/provision/playbook.yml \ + -i terraform-test-environment-module/hosts.ini \ + --private-key=terraform-test-environment-module/${{ steps.terraform-output.outputs.pem_filename }} + env: + ANSIBLE_HOST_KEY_CHECKING: false + + - name: Install Dependencies + working-directory: ./recipes/natural_language_processing/chatbot + run: make install + + - name: Run Integration Tests + working-directory: ./recipes/natural_language_processing/chatbot + run: make integration-tests + env: + URL: ${{ steps.terraform-output.outputs.url }} + + - name: Destroy Test Environment + id: down + if: always() + run: terraform destroy -auto-approve -lock=false + working-directory: terraform-test-environment-module + env: + TF_VAR_aws_instance_type: ${{ matrix.aws_image_type }} + TF_VAR_aws_ami_architecture: ${{ matrix.aws_ami_architecture }} + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + release-images: + runs-on: ubuntu-24.04 + needs: integration-tests + if: success() + strategy: + fail-fast: false + matrix: + include: + - image: llamacpp_python + - image: whispercpp + - image: chatbot + steps: + - name: Login to registry + uses: redhat-actions/podman-login@v1.7 + with: + registry: quay.io + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Copy image from one registry to another one + run: skopeo copy --all docker://${{ env.SOURCE_REGISTRY }}/${{ matrix.image }} docker://${{ env.TARGET_REGISTRY }}/${{ matrix.image }} + env: + SOURCE_REGISTRY: ghcr.io/containers + TARGET_REGISTRY: quay.io/ai-lab + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + test-make-targets: + if: github.repository == 'containers-mirror/ai-lab-recipes' + runs-on: ubuntu-22.04-2core + steps: + - uses: actions/checkout@v4.1.7 + + - name: Set up Python + uses: actions/setup-python@v5.1.1 + with: + python-version: '3.11' + + - name: chatbot + working-directory: ./recipes/natural_language_processing/chatbot + run: make bootc + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/chatbot/.github/workflows/training-e2e.yaml b/chatbot/.github/workflows/training-e2e.yaml new file mode 100644 index 0000000000000000000000000000000000000000..60d052c5adb044a9d0dde3f12f3e7c92dc31e5e7 --- /dev/null +++ b/chatbot/.github/workflows/training-e2e.yaml @@ -0,0 +1,154 @@ +name: traning E2E + +on: + schedule: # schedule the job to run every day at midnight + - cron: '0 12 * * *' + + pull_request: + branches: + - main + paths: + - .github/workflows/training-e2e.yaml + - ./training/** + + workflow_dispatch: + +env: + TF_VAR_aws_region: "eu-west-2" + TF_VAR_aws_ami_owners: '["309956199498"]' + TF_VAR_aws_ami_name: '["*RHEL-9.4*"]' + TF_VAR_aws_volume_size: 500 + TF_VAR_aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} + TF_VAR_aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + HF_TOKEN: ${{ secrets.HF_TOKEN }} + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + e2e: + if: github.repository == 'containers/ai-lab-recipes' && !contains(github.event.pull_request.labels.*.name, 'hold-tests') + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + max-parallel: 1 + matrix: + include: + - arch: amd64 + aws_image_type: g5.8xlarge + image_name: nvidia-bootc + aws_ami_architecture: x86_64 + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + with: + path: main + + - name: Checkout terraform module + id: checkout-module + uses: actions/checkout@v4.1.7 + with: + repository: containers/terraform-test-environment-module + path: terraform-test-environment-module + ref: 'main' + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3.1.2 + with: + terraform_version: "1.7.5" + terraform_wrapper: false + + - name: Init + run: terraform init + working-directory: terraform-test-environment-module + + - name: Bootstrap + id: up + run: terraform apply -auto-approve -lock=false + working-directory: terraform-test-environment-module + env: + TF_VAR_aws_instance_type: ${{ matrix.aws_image_type }} + TF_VAR_aws_ami_architecture: ${{ matrix.aws_ami_architecture }} + + - name: Terraform Output + id: terraform-output + run: | + echo "id=$(terraform output id | xargs)" >> $GITHUB_OUTPUT + echo "url=$(terraform output host | xargs)" >> $GITHUB_OUTPUT + echo "ssh_public_key=$(terraform output ssh_public_key | xargs)" >> $GITHUB_OUTPUT + echo "pem_filename=$(terraform output pem_filename | xargs)" >> $GITHUB_OUTPUT + working-directory: terraform-test-environment-module + + - name: Ansible Collections + run: ansible-galaxy install -r ./tests/provision/requirements.yml + working-directory: ./main/training + + - name: Provision + run: | + ansible-playbook ./main/training/tests/provision/playbook.yml \ + -i terraform-test-environment-module/hosts.ini \ + --private-key=terraform-test-environment-module/${{ steps.terraform-output.outputs.pem_filename }} \ + --extra-vars "image_name=${{ matrix.image_name }}" \ + --extra-vars "ssh_public_key='${{ steps.terraform-output.outputs.ssh_public_key }}'" \ + --extra-vars "registry_user=${{ secrets.REGISTRY_USER }}" \ + --extra-vars "registry_password=${{ secrets.REGISTRY_PASSWORD }}" + env: + ANSIBLE_CONFIG: ./main/training/tests/ansible.cfg + + # - name: Setup tmate session + # uses: mxschmitt/action-tmate@v3.18 + # timeout-minutes: 20 + # with: + # detached: true + # limit-access-to-actor: false + + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3.18 + timeout-minutes: 60 + with: + detached: true + limit-access-to-actor: false + + - name: Run tests + run: | + ansible-playbook ./main/training/tests/e2e-tests/playbook.yml \ + -i terraform-test-environment-module/hosts.ini \ + --private-key=terraform-test-environment-module/${{ steps.terraform-output.outputs.pem_filename }} \ + --extra-vars "HF_TOKEN=${{ secrets.HF_TOKEN }}" \ + --extra-vars "image_name=${{ matrix.image_name }}" \ + --extra-vars "ssh_public_key='${{ steps.terraform-output.outputs.ssh_public_key }}'" \ + --extra-vars "registry_user=${{ secrets.REGISTRY_USER }}" \ + --extra-vars "registry_password=${{ secrets.REGISTRY_PASSWORD }}" + env: + ANSIBLE_CONFIG: ./main/training/tests/ansible.cfg + + # This should exist in the final workflow + # - name: Setup tmate session + # if: ${{ failure() }} + # uses: mxschmitt/action-tmate@v3.18 + # timeout-minutes: 15 + # with: + # detached: true + # limit-access-to-actor: false + + - name: Destroy Test Environment + id: down + if: always() + run: terraform destroy -auto-approve -lock=false + working-directory: terraform-test-environment-module + env: + TF_VAR_aws_instance_type: ${{ matrix.aws_image_type }} + TF_VAR_aws_ami_architecture: ${{ matrix.aws_ami_architecture }} + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/chatbot/.github/workflows/training_bootc.yaml b/chatbot/.github/workflows/training_bootc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b95593ee41999d7005629f32aee93d82c5fe945e --- /dev/null +++ b/chatbot/.github/workflows/training_bootc.yaml @@ -0,0 +1,247 @@ +name: Training Bootc image builds + +on: + push: + branches: [ main ] + paths: + - 'training/**' + - '.github/workflows/training_bootc.yaml' + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + REGISTRY: quay.io + REGISTRY_ORG: ai-lab + REGION: us-east-1 + +jobs: + start-runner: + name: Start self-hosted EC2 runner + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + runs-on: ubuntu-latest + outputs: + label: ${{ steps.start-ec2-runner.outputs.label }} + ec2-instance-id: ${{ steps.start-ec2-runner.outputs.ec2-instance-id }} + steps: + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.REGION }} + - name: Start EC2 runner + id: start-ec2-runner + uses: machulav/ec2-github-runner@v2 + with: + mode: start + github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }} + ec2-image-id: ami-0154957ba4ce98784 + ec2-instance-type: m7i.12xlarge + subnet-id: subnet-0b1e1d94240813658 + security-group-id: sg-055105753f5e8bd83 + + nvidia-bootc-builder-image: + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + strategy: + matrix: + include: + - image_name: nvidia-builder + context: training/nvidia-bootc + arch: amd64 + runs-on: ${{ needs.start-runner.outputs.label }} + needs: start-runner + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4.1.7 + + - name: mkdir root/.docker directory + run: | + mkdir -p ~/.docker + + - name: Login to Container Registry + run: podman login -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_PASSWORD }} ${{ env.REGISTRY }} + + - name: generate a ssh key - USER SHOULD INJECT THEIR OWN AND REBUILD IF THEY USE THIS IMAGE + run: | + ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N "" + + - name: Build Image + id: build_image + run: make driver-toolkit ARCH=${{ matrix.arch }} + working-directory: ${{ matrix.context }} + + - name: tag image as nvidia-builder + run: podman tag ${{ env.REGISTRY }}/${{ env.REGISTRY_ORG }}/driver-toolkit:latest ${{ env.REGISTRY }}/${{ env.REGISTRY_ORG }}/${{ matrix.image_name}}:latest + + - name: Push image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + registry: ${{ env.REGISTRY }}/${{ env.REGISTRY_ORG }} + image: driver-toolkit + tags: latest + + - name: push the nvidia-builder image + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: redhat-actions/push-to-registry@v2.8 + with: + image: ${{ matrix.image_name}} + tags: latest + registry: ${{ env.REGISTRY }}/${{ env.REGISTRY_ORG }} + + - name: Publish Job Results to Slack + id: slack + if: always() + uses: slackapi/slack-github-action@v1.26.0 + with: + payload: | + { + "text": "${{ github.workflow }} workflow status: ${{ job.status }}\n${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + nvidia-bootc-image: + strategy: + matrix: + include: + - image_name: nvidia-bootc + driver_version: "550.54.15" + context: training/nvidia-bootc + arch: amd64 + runs-on: ${{ needs.start-runner.outputs.label }} + if: "!contains(github.event.pull_request.labels.*.name, 'hold-tests')" + needs: [ nvidia-bootc-builder-image, start-runner ] + steps: + - uses: actions/checkout@v4.1.7 + + - name: mkdir root/.docker directory + run: | + mkdir -p ~/.docker + + - name: generate a ssh key - USER SHOULD INJECT THEIR OWN AND REBUILD IF THEY USE THIS IMAGE and overwrite the existing one + run: | + ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N "" <<`. + +Inside of the new directory you should add the following files: + +* `ai-lab.yaml` +* `app/Containerfile` +* `app/requirements.txt` +* `_ui.py` +* `README.md` + +### ai-lab.yaml + +This is the most critical file in our directory as in a sense it _IS_ the recipe. This yaml file dictates which images make up our AI application and where their container files can be found. Below please see the chabot example. + +```yaml +version: v1.0 +application: + type: language + name: ChatBot_Streamlit + description: This is a Streamlit chat demo application. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: streamlit-chat-app + contextdir: . + containerfile: app/Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/chatbot:latest +``` + +You can use this example as your template and change the fields where needed to define your own recipe. + +### app/Containerfile + +This will be the Containerfile used to build the client side image of your AI application. Whenever possible, we will use Red Hat's UBI as our base image. Below please see an example from the chatbot recipe. + +```Dockerfile +FROM registry.access.redhat.com/ubi9/python-311:1-52 +WORKDIR /chat +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /chat/requirements.txt +COPY chatbot_ui.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "chatbot_ui.py" ] +``` + +You can use this example as your template and change the fields where needed to define your own Containerfile. + +### app/requirements.txt + +You need to include a requirements.txt file here as well so that we ensure the correct dependencies get built into our application. + +### _ui.py + +This is the client code you write to interact with the model service. This is the piece of code that will give your recipe it's unique behavior and interface. Currently, all the recipes are written in python so we've used the `*.py` extension here. But we are happy to include recipes that use other languages as well. + +### README.MD + +Every recipe needs a README.md that specifies what the recipe's application does and how to build, deploy and interact with it. + +### _Where's the model server?_ +After creating your new recipe by adding the files above you might be asking yourself where the model and model servers are? It turns out that there are many more unique AI applications than there are model servers, and many of the AI applications from the same category can all use the same model server. So, instead of replicating this code for every recipe, we maintain a separate directory for our model servers and point to the desired one in the `ai-lab.yaml`. + + +## Contributing New Model Servers + +There are a number of options out there for model servers and we want to ensure that we provide developers with a variety of vetted options for the model server that will meet their application's needs. + +Deciding which model server is right for a particular use case primarily comes down to the kind of model you want to use (LLM, Object Detection, Data Classification, etc.) and the resources available (GPU, CPU, Cloud, Local). + +### Adding a New Model Server + +All of the documentation, scripts and Containerfiles for our model servers live in `model_servers/`. If you would like to contribute a new model server, please create a new directory called `model_servers/`. + +Inside of this directory you should at a minimum add the following: + +* `base/Containerfile` +* `README.md` + +Depending on the specific needs of the model server, there may be additional files needed to build it. Please see [model_server/llamacpp_python](model_servers/llamacpp_python/) for a more complex example. + +### base/Containerfile + +This will be the Containerfile used to build the model server. Whenever possible, we will use Red Hat's UBI as our base image. Below see an example for the base llamacpp_python model server. + +```Dockerfile +FROM registry.access.redhat.com/ubi9/python-311:1-52 +WORKDIR /locallm +COPY src . +RUN pip install --no-cache-dir --verbose -r ./requirements.txt +EXPOSE 8001 +ENTRYPOINT [ "sh", "./run.sh" ] +``` +You can use this example as your template and change the fields where needed to define your own Containerfile. + +If a model service requires different build instructions for different hardware environments, they can be added under `model_servers/` as well. For example adding a Containerfile for building CUDA enabled images for Nvidia GPU's would be added as `cuda/Containerfile`. + +### README.md + +Every model server needs a README.md that specifies how to build, deploy and interact with it. Include any recipes that use this model server or how a recipe would need to be modified to use it. + + +## Contributing New Vector Databases + +Although the model server and AI client are the minimum required components for an AI infused application, there are other tools and technologies in the AI ecosystem that users may want to include into their application to enhance the capabilities of their AI. One such tool is a vector database. + +There are many options out there for vector databases and we would like to provide users with a number of well vetted options along with recipes that indicate how to best utilize them. + +Once a vector database is added to the repo it can be included as an additional `container` in the `ai-lab.yaml` file of any appropriate recipe. + +### Adding a New Vector Database + +All of the documentation, scripts and Containerfiles for our vector databases live in `vector_dbs/`. If you would like to contribute a new vector database, please create a new directory called `vector_dbs/`. + +Inside of this directory you should at a minimum add the following: + +* `base/Containerfile` +* `README.md` + +Depending on the specific needs of the vector database, there may be additional files needed to build it. + +### base/Containerfile + +This will be the Containerfile used to build the vector database. Whenever possible, we will use Red Hat's UBI as our base image. + +If a vector database requires different build instructions for different hardware environments, they can be added under `vector_dbs/` as well. For example adding a Containerfile for building CUDA enabled images for Nvidia GPU's would be added as `cuda/Containerfile`. + +### README.md + +Every vector database needs a README.md that specifies how to build, deploy and interact with it. Include any recipes that use this vector database or how a recipe would need to be modified to use it. + + +## Additional Contributions + +If you would like to contribute in some other way not outlined here, please feel free to open a [PR](https://github.com/containers/ai-lab-recipes/pulls) or an [Issue](https://github.com/containers/ai-lab-recipes/issues) in this repository and one of our maintainers will follow up. Thanks! diff --git a/chatbot/LICENSE b/chatbot/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..ed82ffe12f60161d1f9cba8250513a0a0cf2b2d5 --- /dev/null +++ b/chatbot/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/chatbot/README.md b/chatbot/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4679efc98046cec1eea51ac05d3a029d1dc92f3d --- /dev/null +++ b/chatbot/README.md @@ -0,0 +1,70 @@ +# AI Lab Recipes + +![](/assets/ai_lab_recipes_logo.png) + +This repo contains recipes for building and running containerized AI and LLM +Applications with Podman. + +These containerized AI recipes can be used to help developers quickly prototype +new AI and LLM based applications locally, without the need for relying on any other +externally hosted services. Since they are already containerized, it also helps +developers move quickly from prototype to production. + +## Model servers + +#### What's a model server? + +A model server is a program that serves machine-learning models, such as LLMs, and +makes their functions available via an API. This makes it easy for developers to +incorporate AI into their applications. This repository provides descriptions and +code for building several of these model servers. + +Many of the sample applications rely on the `llamacpp_python` model server by +default. This server can be used for various generative AI applications with various models. +However, each sample application can be paired with a variety of model servers. + +Learn how to build and run the llamacpp_python model server by following the +[llamacpp_python model server README](/model_servers/llamacpp_python/README.md). + +## Current Recipes + +Recipes consist of at least two components: A model server and an AI application. +The model server manages the model, and the AI application provides the specific +logic needed to perform some specific task such as chat, summarization, object +detection, etc. + +There are several sample applications in this repository that can be found in the +[recipes](./recipes) directory. + +They fall under the categories: + +* [audio](./recipes/audio) +* [computer-vision](./recipes/computer_vision) +* [multimodal](./recipes/multimodal) +* [natural language processing](./recipes/natural_language_processing) + + +Learn how to build and run each application by visiting their README's. +For example, learn how to run the [chatbot recipe here](./recipes/natural_language_processing/chatbot). + +## Current AI Lab Recipe images built from this repository + +Images for many sample applications and models are available in `quay.io`. All +currently built images are tracked in +[ailab-images.md](./ailab-images.md) + +## [Training](./training/README.md) + +Linux Operating System Bootable containers enabled for AI Training + +## Setting Up Git Hooks + +To install our standard git hooks, run the following command: + +```sh +./install-hooks.sh +``` + +### pre-commit hook + +Ensures that `training/ilab-wrapper/ilab` is duplicated into `training/nvidia-bootc/duplicated/ilab-wrapper/ilab` diff --git a/chatbot/ailab-images.md b/chatbot/ailab-images.md new file mode 100644 index 0000000000000000000000000000000000000000..13f6a3b93e657923a1ae4c7e18be9100f25e5054 --- /dev/null +++ b/chatbot/ailab-images.md @@ -0,0 +1,34 @@ +## Model Server Images (amd64, arm64) currently built from GH Actions in this repository + +- quay.io/ai-lab/llamacpp_python:latest +- quay.io/ai-lab/llamacpp-python-cuda:latest +- quay.io/ai-lab/llamacpp-python-vulkan:latest +- quay.io/redhat-et/locallm-object-detection-server:latest + +## Recipe Images (amd64, arm64) +- quay.io/ai-lab/summarizer:latest +- quay.io/ai-lab/chatbot:latest +- quay.io/ai-lab/rag:latest +- quay.io/ai-lab/codegen:latest +- quay.io/redhat-et/locallm-object-detection-client:latest + +## Dependency images (amd64) + +Images used in the `Bootc` aspect of this repo or tooling images + +- quay.io/ai-lab/chromadb:latest +- quay.io/ai-lab/model-converter:latest + +## Model Images (amd64, arm64) + +- quay.io/ai-lab/merlinite-7b-lab:latest + - [model download link](https://huggingface.co/instructlab/merlinite-7b-lab-GGUF/resolve/main/merlinite-7b-lab-Q4_K_M.gguf) +- quay.io/ai-lab/granite-7b-lab:latest + - [model download link](https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf) +- quay.io/ai-lab/mistral-7b-instruct:latest + - [model download link](https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf) +- quay.io/ai-lab/mistral-7b-code-16k-qlora:latest + - [model download link](https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF/resolve/main/mistral-7b-code-16k-qlora.Q4_K_M.gguf) +- quay.io/ai-lab/whisper-small:latest + - [model download link](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin) + diff --git a/chatbot/assets/ai_lab_recipes_logo.png b/chatbot/assets/ai_lab_recipes_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..45986972b643ce99b7f2ecd37634d4bcad802a31 --- /dev/null +++ b/chatbot/assets/ai_lab_recipes_logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bfd9ce904c90671b9d8ca12ddb54f96422fc1aa13a9b94b86af15ced8d4d23b0 +size 209956 diff --git a/chatbot/assets/chatbot_nodejs_ui.png b/chatbot/assets/chatbot_nodejs_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..a2500a5532116e9caffb62e90d6992ba1f8349a3 Binary files /dev/null and b/chatbot/assets/chatbot_nodejs_ui.png differ diff --git a/chatbot/assets/chatbot_ui.png b/chatbot/assets/chatbot_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc7c6a303e0157851f016c201c667b5619aef6a --- /dev/null +++ b/chatbot/assets/chatbot_ui.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c517e39edb3aa6c42db60d37886196d5a975f67eaf12acb9007409c87407c1a +size 174521 diff --git a/chatbot/assets/codegen_ui.png b/chatbot/assets/codegen_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..1bd8d2326c1156ce33b46f86824d13825fa10609 --- /dev/null +++ b/chatbot/assets/codegen_ui.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51037f5e7824ce92992dc5ee76f9d5d6abeb662c93084f2d394c726f066baf75 +size 276610 diff --git a/chatbot/assets/image_analysis.png b/chatbot/assets/image_analysis.png new file mode 100644 index 0000000000000000000000000000000000000000..eeb4728360f9a0d61fa0dcd3a91a3d4cbd58fcae --- /dev/null +++ b/chatbot/assets/image_analysis.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fdefcbab9c9a4a873587d2283f400b5e6b8783af25e86dd1aac1342a3618bb3 +size 554947 diff --git a/chatbot/assets/install_continue_extension.png b/chatbot/assets/install_continue_extension.png new file mode 100644 index 0000000000000000000000000000000000000000..d803e82eb73d94a50404ab3dc33f68360c178141 --- /dev/null +++ b/chatbot/assets/install_continue_extension.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5d4e58dea593a34d73c33a5556e494a16bfff7eb4a01e5f0610917547c3c544 +size 517396 diff --git a/chatbot/assets/model_converter.png b/chatbot/assets/model_converter.png new file mode 100644 index 0000000000000000000000000000000000000000..31d2a65088a4d1fd2ce4025cd84080a55777be49 Binary files /dev/null and b/chatbot/assets/model_converter.png differ diff --git a/chatbot/assets/object_detection.png b/chatbot/assets/object_detection.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c86e2903c60d2191e0f5db91c6bd648f74eabb --- /dev/null +++ b/chatbot/assets/object_detection.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:801927e2ec6e4d7a125f842b267f9d3d551470cc4724333975f425a47e2b128c +size 779238 diff --git a/chatbot/assets/rag_ui.png b/chatbot/assets/rag_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..135a6d53873dccc50c36363b086fb49c13644a00 --- /dev/null +++ b/chatbot/assets/rag_ui.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:501ef08f714b2b759c7472e6416a2e05f4a3979fd775c58c5be35ace1b35effd +size 194937 diff --git a/chatbot/assets/summarizer_ui.png b/chatbot/assets/summarizer_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..7b8286d163a4458698ce0f3c00e9cfc5df37e79a --- /dev/null +++ b/chatbot/assets/summarizer_ui.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b0c1c00c3bb65df4a99c6b1c77b18a6efe2d88e4018b35e0a8770901b2ef751 +size 170105 diff --git a/chatbot/assets/whisper.png b/chatbot/assets/whisper.png new file mode 100644 index 0000000000000000000000000000000000000000..7701b261e7d40e98e2888225e309d960d2a3a5e9 --- /dev/null +++ b/chatbot/assets/whisper.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cdd71ceceba988dc396d66ad1dfe8c7eb2702554ebfc7204841b7ff057134e1 +size 206670 diff --git a/chatbot/ci/trace-steps.py b/chatbot/ci/trace-steps.py new file mode 100644 index 0000000000000000000000000000000000000000..07db060673364345434081fa10cbdec196bac2b6 --- /dev/null +++ b/chatbot/ci/trace-steps.py @@ -0,0 +1,47 @@ +import os +import time +from datetime import datetime +from opentelemetry import trace +from opentelemetry.sdk.resources import Resource +from opentelemetry.sdk.trace import TracerProvider +from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter +from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter + +service_name = os.getenv("WORKFLOW_NAME", "default_service") +job_name = os.getenv("JOB_NAME", "default_job") + +resource = Resource.create({"service.name": service_name}) +trace.set_tracer_provider(TracerProvider(resource=resource)) +tracer = trace.get_tracer(__name__) +console_span_processor = BatchSpanProcessor(ConsoleSpanExporter()) +trace.get_tracer_provider().add_span_processor(console_span_processor) + +# Adding OTLP Span Exporter for actual data export +otlp_exporter = OTLPSpanExporter(endpoint="localhost:4317", insecure=True) +otlp_span_processor = BatchSpanProcessor(otlp_exporter) +trace.get_tracer_provider().add_span_processor(otlp_span_processor) + +print("Tracer initialized with service name:", service_name) + +def set_start_time(): + start_time = datetime.now().timestamp() + with open("/tmp/start_time.txt", "w") as file: + file.write(str(start_time)) + print("Start time recorded") + +def calculate_duration(): + with open("/tmp/start_time.txt", "r") as file: + start_time = float(file.read()) + end_time = datetime.now().timestamp() + duration = end_time - start_time + print(f"Total Duration: {duration}s") + with tracer.start_as_current_span(job_name) as span: + span.set_attribute("total_duration_s", duration) + +if __name__ == "__main__": + action = os.getenv("TRACE_ACTION", "start") + + if action == "start": + set_start_time() + elif action == "end": + calculate_duration() diff --git a/chatbot/convert_models/Containerfile b/chatbot/convert_models/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..069c6465a6a64b8ed20079bfe846309928e609f4 --- /dev/null +++ b/chatbot/convert_models/Containerfile @@ -0,0 +1,10 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /opt/app-root/src/converter +USER root +RUN chown -R default:root /opt/app-root/src/converter +USER default +RUN git clone https://github.com/ggerganov/llama.cpp.git +RUN cd llama.cpp/ && make +RUN pip install -r llama.cpp/requirements.txt +COPY . /opt/app-root/src/converter/ +ENTRYPOINT ["sh", "run.sh"] diff --git a/chatbot/convert_models/README.md b/chatbot/convert_models/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8610f5cabe64108f3384dc28b7e502da3245fba5 --- /dev/null +++ b/chatbot/convert_models/README.md @@ -0,0 +1,44 @@ +# Convert and Quantize Models + +AI Lab Recipes' default model server is [llamacpp_python](https://github.com/abetlen/llama-cpp-python), which needs models to be in a `*.GGUF` format. + +However, most models available on [huggingface](https://huggingface.co/models) are not provided directly as `*.GGUF` files. More often they are provided as a set of `*.bin` or `*.safetensor` files with some additional metadata produced when the model is trained. + +There are of course a number of users on huggingface who provide `*.GGUF` versions of popular models. But this introduces an unnecessary interim dependency as well as possible security or licensing concerns. + +To avoid these concerns and provide users with the maximum freedom of choice for their models, we provide a tool to quickly and easily convert and quantize a model from huggingface into a `*.GGUF` format for use with our `*.GGUF` compatible model servers. + +![](/assets/model_converter.png) + +## Build the Container Image + +```bash +cd convert_models +podman build -t converter . +``` + +## Quantize and Convert + +You can run the conversion image directly with podman in the terminal. You just need to provide it with the huggingface model name you want to download, the quantization level you want to use and whether or not you want to keep the raw files after conversion. "HF_TOKEN" is optional, it is required for private models. + +```bash +podman run -it --rm -v models:/converter/converted_models -e HF_MODEL_URL= -e HF_TOKEN= -e QUANTIZATION=Q4_K_M -e KEEP_ORIGINAL_MODEL="False" converter +``` + +You can also use the UI shown above to do the same. + +```bash +streamlit run convert_models/ui.py +``` + +## Model Storage and Use + +This process writes the models into a podman volume under a `gguf/` directory and not directly back to the user's host machine (This could be changed in an upcoming update if it is required). + +If a user wants to access these models to use with the llamacpp_python model server, they would simply point their model service to the correct podman volume at run time. For example: + +```bash +podman run -it -p 8001:8001 -v models:/opt/app-root/src/converter/converted_models/gguf:Z -e MODEL_PATH=/gguf/ -e HOST=0.0.0.0 -e PORT=8001 llamacpp_python +``` + + diff --git a/chatbot/convert_models/download_huggingface.py b/chatbot/convert_models/download_huggingface.py new file mode 100644 index 0000000000000000000000000000000000000000..008c536a034b215058d67a88d748bcf88f102b82 --- /dev/null +++ b/chatbot/convert_models/download_huggingface.py @@ -0,0 +1,13 @@ +from huggingface_hub import snapshot_download +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-m", "--model") +parser.add_argument("-t", "--token") +args = parser.parse_args() + +snapshot_download(repo_id=args.model, + token=args.token, + local_dir=f"converted_models/{args.model}", + local_dir_use_symlinks=True, + cache_dir=f"converted_models/cache") \ No newline at end of file diff --git a/chatbot/convert_models/requirements.txt b/chatbot/convert_models/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..b2642b32880c428fa0e39156712a15479b556995 --- /dev/null +++ b/chatbot/convert_models/requirements.txt @@ -0,0 +1 @@ +huggingface_hub \ No newline at end of file diff --git a/chatbot/convert_models/run.sh b/chatbot/convert_models/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..a14214f8ef19cbb5448707449637de61c072fc3b --- /dev/null +++ b/chatbot/convert_models/run.sh @@ -0,0 +1,32 @@ +#! /bin/bash + +hf_model_url=${HF_MODEL_URL} +hf_token=${HF_TOKEN:="None"} +model_org=$(echo $hf_model_url | sed -n 's/\(.*\)\/\(.*\)/\1/p') +model_name=$(echo $hf_model_url | sed -n 's/\(.*\)\/\(.*\)/\2/p') +keep_orgi=${KEEP_ORIGINAL_MODEL} + +if [ -e "/opt/app-root/src/converter/converted_models/gguf/$model_org-$model_name-${QUANTIZATION}.gguf" ]; then + echo "$model_org-$model_name-${QUANTIZATION}.gguf already exists... skipping" + exit 0 +fi + +if [ -e "/opt/app-root/src/converter/converted_models/cache/models--$model_org--$model_name" ]; then + echo "$hf_model_url present in cache... skipping download" +fi + +echo "Downloading $hf_model_url" +python download_huggingface.py --model $hf_model_url --token $hf_token +python llama.cpp/examples/convert_legacy_llama.py /opt/app-root/src/converter/converted_models/$hf_model_url +python llama.cpp/convert_hf_to_gguf.py /opt/app-root/src/converter/converted_models/$hf_model_url +mkdir -p /opt/app-root/src/converter/converted_models/gguf/ +llama.cpp/llama-quantize /opt/app-root/src/converter/converted_models/$hf_model_url/ggml-model-f16.gguf /opt/app-root/src/converter/converted_models/gguf/$model_org-$model_name-${QUANTIZATION}.gguf ${QUANTIZATION} +rm -rf /opt/app-root/src/converter/converted_models/$model_org + +if [ $keep_orgi = "False" ]; then + rm -rf /opt/app-root/src/converter/converted_models/cache +fi + +echo "Converted and quantized model written to /opt/app-root/src/converter/converted_models/gguf/$model_org-$model_name.gguf" +echo "$ ls /opt/app-root/src/converter/converted_models/gguf/" +ls /opt/app-root/src/converter/converted_models/gguf/ diff --git a/chatbot/convert_models/ui.py b/chatbot/convert_models/ui.py new file mode 100644 index 0000000000000000000000000000000000000000..3f5396926d9a2ffba734ec11e5dd65b578d6daa1 --- /dev/null +++ b/chatbot/convert_models/ui.py @@ -0,0 +1,55 @@ +import streamlit as st +import subprocess +import os + + +quantization_types = ["Q2_K","Q3_K_S","Q3_K_M", "Q3_K_L", "Q4_K_S", + "Q4_K_M", "Q5_K_S", "Q5_K_M", "Q6_K"] + +st.title("🤗 GGUF Model Converter") + +with st.sidebar: + st.markdown("Tested Models:") + st.code("TBD") + +col1, col2 =st.columns(2) +with col1: + volume = st.text_input(label="Volume Name", + placeholder="models",) +with col2: + quantization = st.selectbox(label="Quantization Level", + options=quantization_types,index=5) + +model_name = st.text_input(label="Enter a huggingface model url to convert", + placeholder="org/model_name") +token_id = st.text_input(label="Enter your huggingface token (optional)", + help="huggingface token is required for private model" + ) or "None" +keep_files = st.checkbox("Keep huggingface model files after conversion?") +submit_button = st.button(label="submit") +if submit_button: + with st.spinner("Processing Model..."): + x = subprocess.Popen(["podman", + "run", + "-it", + "--rm", + "-v", f"{volume}:/converter/converted_models", + "-e", f"HF_MODEL_URL={model_name}", + "-e", f"HF_TOKEN={token_id}", + "-e", f"QUANTIZATION={quantization}", + "-e", f"KEEP_ORIGINAL_MODEL={keep_files}", + "converter"],stdout=subprocess.PIPE) + + container_output = st.empty() + response = [] + num_lines=0 + while x.poll() is None: + line = x.stdout.readline().decode() + num_lines += 1 + response.append(line) + if num_lines < 21: + container_output.code("".join(response), + language="Bash") + else: + container_output.code("".join(response[num_lines-21:num_lines]), + language="Bash") diff --git a/chatbot/data/fake_meeting.pdf b/chatbot/data/fake_meeting.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6ce4dc7fdfd9288bf56ee5026cf247f6852db5af Binary files /dev/null and b/chatbot/data/fake_meeting.pdf differ diff --git a/chatbot/data/fake_meeting.txt b/chatbot/data/fake_meeting.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1a6463e04bc8dd295044e51a769bda9e3fe50e3 --- /dev/null +++ b/chatbot/data/fake_meeting.txt @@ -0,0 +1,29 @@ +[The scene is set in a luxurious conference room with the three executives seated around a large oak table. The room is well-lit and the atmosphere is professional and cordial.] +Executive 1: "Good morning, everyone. Thank you for joining me today to discuss our exciting new AI business venture." +Executive 2: "Of course, John. I'm thrilled to be here. This is a game-changer for our college and I can't wait to see it come to fruition." +Executive 3: "Indeed. As you know, AI is becoming increasingly important in various industries, and we believe that our venture will provide significant benefits to both our students and the business world as a whole." +Executive 1: "That's right. Our AI platform will offer personalized learning experiences for our students, tailored to their individual needs and goals. And for the business world, it will provide cutting-edge insights and predictions based on vast amounts of data, giving them a competitive edge in today's fast-paced marketplace." +Executive 2: "I see. So how do you plan to monetize this platform?" +Executive 3: "That's a great question. We plan to offer subscription-based services to businesses, as well as generate revenue through partnerships and collaborations with industry leaders. Additionally, we will also explore opportunities for licensing our AI technology to other organizations." +Executive 1: "Excellent. And what about security and privacy concerns? How do you plan to address those?" +Executive 2: "Absolutely. We understand the importance of protecting sensitive data, and we will implement robust security measures to ensure that our platform is secure and compliant with all relevant regulations." +Executive 3: "Yes, and we will also have strict data privacy policies in place to safeguard the personal information of our students and clients. Transparency and trust are key components of any successful AI venture, and we take those seriously." +Executive 1: "I couldn't agree more. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +[The three executives nod in agreement and begin brainstorming strategies for promoting their AI platform.] +Executive 1: "Absolutely. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +Executive 2: "Agreed. We should start by reaching out to industry leaders and thought leaders in the field of AI and education. They will be key in helping us spread the word and build momentum for our platform." +Executive 3: "Excellent idea. And we should also consider partnering with some of the leading AI research institutions and universities. They will be able to provide valuable insights and expertise that will help us refine and improve our platform." +Executive 1: "That's a great point. Partnerships are key in any successful venture, and we want to make sure that we're working with the best of the best in this field." +Executive 2: "Definitely. And once we have a solid proof of concept, we can start reaching out to potential clients and showcasing the value of our platform. I think we'll find a lot of interest from companies looking for innovative ways to improve their operations and stay ahead of the competition." +Executive 3: "I agree. And as we continue to develop and refine our platform, we can also start exploring new markets and applications for AI in education. There are so many possibilities here, and I'm excited to see where this journey takes us." +Certainly! Here is a continuation of the dialogue: +Executive 1: "Absolutely. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +Executive 2: "Agreed. We should start by reaching out to industry leaders and thought leaders in the field of AI and education. They will be key in helping us spread the word and build momentum for our platform." +Executive 3: "Excellent idea. And we should also consider partnering with some of the leading AI research institutions and universities. They will be able to provide valuable insights and expertise that will help us refine and improve our platform." +Executive 1: "That's a great point. Partnerships are key in any successful venture, and we want to make sure that we're working with the best of the best in this field." +Executive 2: "Definitely. And once we have a solid proof of concept, we can start reaching out to potential clients and showcasing the value of our platform. I think we'll find a lot of interest from companies looking for innovative ways to improve their operations and stay ahead of the competition." +Executive 3: "I agree. And as we continue to develop and refine our platform, we can also start exploring new markets and applications for AI in education. There are so many possibilities here, and I'm excited to see where this journey takes us." +Executive 1: "Absolutely. And speaking of markets, let's not forget about the potential for international expansion. We could be looking at a global market opportunity here, and we don't want to miss out on that." +Executive 2: "Agreed. We should definitely consider how we can tailor our platform to meet the unique needs of different cultures and regions around the world." +Executive 3: "Excellent point. And as we continue to grow and expand, we'll need to make sure that we have the right infrastructure in place to support our global ambitions." +[The three executives nod in agreement and begin brainstorming strategies for promoting their AI platform on a global scale.] \ No newline at end of file diff --git a/chatbot/data/jfk.wav b/chatbot/data/jfk.wav new file mode 100644 index 0000000000000000000000000000000000000000..55cce053bc07ff8144ef872ebe374034eabcdaf0 --- /dev/null +++ b/chatbot/data/jfk.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59dfb9a4acb36fe2a2affc14bacbee2920ff435cb13cc314a08c13f66ba7860e +size 352078 diff --git a/chatbot/eval/embeddings/custom_eval_set.py b/chatbot/eval/embeddings/custom_eval_set.py new file mode 100644 index 0000000000000000000000000000000000000000..4adfdee4f8d3dade85bc35c96666fd478e2f545a --- /dev/null +++ b/chatbot/eval/embeddings/custom_eval_set.py @@ -0,0 +1,98 @@ +from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings +from langchain_openai import ChatOpenAI +from langchain.chains import LLMChain +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +import matplotlib.pyplot as plt +import os +from scipy.spatial.distance import cosine +import streamlit as st + + +model_service = os.getenv("MODEL_ENDPOINT", + "http://localhost:8001") +model_service = f"{model_service}/v1" + +embedding_model = os.getenv("EMBEDDING_MODEL", + "BAAI/bge-base-en-v1.5") + +def get_embedding(string, e): + embeddings = e.embed_query(string) + return embeddings + + +st.title("📊 Create Custom LLM Eval Set") + +if "Question" not in st.session_state: + st.session_state["Question"] = "What is the Higgs Boson?" + +if "Answers" not in st.session_state: + st.session_state["Answers"] = {} + st.session_state["Answers"]["Right_Answer_1"] = "The Higgs boson, sometimes called the Higgs particle, is an elementary particle in the Standard Model of particle physics produced by the quantum excitation of the Higgs field, one of the fields in particle physics theory" + st.session_state["Answers"]["Wrong_Answer_1"] = "Alan Turing was the first person to conduct substantial research in the field that he called machine intelligence." + +st.session_state["Question"] = st.text_input(label="Question", value=st.session_state["Question"]) + +col1,col2,col3 = st.columns(3) +with col1: + st.session_state["Answers"]["Right_Answer_1"] = st.text_input("Right Answer 1", + value=st.session_state["Answers"]["Right_Answer_1"]) +with col2: + st.session_state["Answers"]["Right_Answer_2"] = st.text_input("Right Answer 2") + +with col3: + st.session_state["Answers"]["Right_Answer_3"] = st.text_input("Right Answer 3") + + +col1,col2,col3 = st.columns(3) +with col1: + st.session_state["Answers"]["Wrong_Answer_1"] = st.text_input("Wrong Answer 1", + value=st.session_state["Answers"]["Wrong_Answer_1"]) +with col2: + st.session_state["Answers"]["Wrong_Answer_2"] = st.text_input("Wrong Answer 2") +with col3: + st.session_state["Answers"]["Wrong_Answer_3"] = st.text_input("Wrong Answer 3") + + +text = {k:[v] for (k,v) in st.session_state["Answers"].items() if v != ""} +text["Question"] = [st.session_state["Question"]] +e = SentenceTransformerEmbeddings(model_name=embedding_model) + +for t in text.keys(): + text[t].append(get_embedding(text[t][0],e)) + +answer_embedding = text["Question"][1] + +for t in text.keys(): + question_embedding = text[t][1] + distance = cosine(answer_embedding, question_embedding) + text[t].append(round(distance,3)) + +distances = [text[key][2] for key in text.keys()] +ones = [1]* len(distances) +fig = plt.figure() +plt.vlines(1,.001,1) +plt.scatter(ones, distances) +for key in text.keys(): + plt.annotate(key,(1, text[key][2])) +plt.xticks([]) +plt.ylabel("Cosine Similarity") +st.pyplot(fig) + +submit = st.button("Check Against Model") +if submit: + llm = ChatOpenAI(base_url=model_service, + api_key="sk-no-key-required") + + prompt = ChatPromptTemplate.from_messages([ + ("system", "You are world class technical advisor."), + ("user", "{input}")]) + + chain = LLMChain(llm=llm, + prompt=prompt, + verbose=False,) + + response = chain.invoke(st.session_state["Question"]) + st.session_state["Answers"]["LLM Response"] = response["text"] + st.markdown(st.session_state["Answers"]["LLM Response"]) + st.rerun() + \ No newline at end of file diff --git a/chatbot/eval/promptfoo/README.md b/chatbot/eval/promptfoo/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9e5554832a8f8bce58e11f4adaf7b0998ef7fb90 --- /dev/null +++ b/chatbot/eval/promptfoo/README.md @@ -0,0 +1,15 @@ +# LLM Evaluation with Promptfoo + +We are using the [Promptfoo.dev](https://www.promptfoo.dev/) project for LLM model evaluation. + +``` + podman build -t promptfoo eval/promptfoo/build +``` + +Make sure you are running an LLM before starting the promptfoo container. + +``` +podman run -it -p 15500:15500 -v /locallm/eval/promptfoo/evals/:/promptfoo/evals:ro promptfoo +``` + +Go to `http://0.0.0.0:15500/setup/` to set up your tests. \ No newline at end of file diff --git a/chatbot/eval/promptfoo/base/Containerfile b/chatbot/eval/promptfoo/base/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..9bea473110ccb07d313f5aa48832c7601bec7113 --- /dev/null +++ b/chatbot/eval/promptfoo/base/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/nodejs-20-minimal:1-63.1725851021 +WORKDIR /promptfoo +RUN npm install promptfoo +ENV PROMPTFOO_DISABLE_TELEMETRY=1 +RUN mkdir evals +ENV PROMPTFOO_CONFIG_DIR=/promptfoo/evals +COPY promptfooconfig.yaml /promptfoo +ENTRYPOINT [ "npx", "promptfoo@latest", "view", "--yes" ] diff --git a/chatbot/eval/promptfoo/base/promptfooconfig.yaml b/chatbot/eval/promptfoo/base/promptfooconfig.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b2bae5ad6ff24bcd5b14f7875538fd888e9a4328 --- /dev/null +++ b/chatbot/eval/promptfoo/base/promptfooconfig.yaml @@ -0,0 +1,31 @@ +# This configuration compares LLM output of 2 prompts x 2 GPT models across 3 test cases. +# Learn more: https://promptfoo.dev/docs/configuration/guide +description: 'My first eval' + +prompts: + - "Write a tweet about {{topic}}" + - "Write a very concise, funny tweet about {{topic}}" + +providers: + - openai:gpt-3.5-turbo-0613 + - openai:gpt-4 + +tests: + - vars: + topic: bananas + + - vars: + topic: avocado toast + assert: + # For more information on assertions, see https://promptfoo.dev/docs/configuration/expected-outputs + - type: icontains + value: avocado + - type: javascript + value: 1 / (output.length + 1) # prefer shorter outputs + + - vars: + topic: new york city + assert: + # For more information on model-graded evals, see https://promptfoo.dev/docs/configuration/expected-outputs/model-graded + - type: llm-rubric + value: ensure that the output is funny diff --git a/chatbot/eval/promptfoo/evals/README.md b/chatbot/eval/promptfoo/evals/README.md new file mode 100644 index 0000000000000000000000000000000000000000..74ce85359ae3668ba9e77de6a0effef2f336c673 --- /dev/null +++ b/chatbot/eval/promptfoo/evals/README.md @@ -0,0 +1 @@ +Directory to store evaluation runs locally \ No newline at end of file diff --git a/chatbot/hooks/pre-commit b/chatbot/hooks/pre-commit new file mode 100644 index 0000000000000000000000000000000000000000..45140eec2b58d288c08c04f24bef95226cd4c131 --- /dev/null +++ b/chatbot/hooks/pre-commit @@ -0,0 +1,13 @@ +#!/bin/bash + +SOURCE_FILE="training/ilab-wrapper/ilab" +DEST_FILE="training/nvidia-bootc/duplicated/ilab-wrapper/ilab" + +if [[ -f "$SOURCE_FILE" ]]; then + mkdir -p "$(dirname "$DEST_FILE")" + cp "$SOURCE_FILE" "$DEST_FILE" + git add "$DEST_FILE" +else + echo "Source file $SOURCE_FILE does not exist. Aborting commit." + exit 1 +fi diff --git a/chatbot/install-hooks.sh b/chatbot/install-hooks.sh new file mode 100644 index 0000000000000000000000000000000000000000..2c04220c63de1607cdc2401519b37a0a1f882ecf --- /dev/null +++ b/chatbot/install-hooks.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +HOOKS_DIR="hooks" +GIT_HOOKS_DIR=".git/hooks" + +cp "$HOOKS_DIR/pre-commit" "$GIT_HOOKS_DIR/pre-commit" + +echo "Hooks installed successfully." diff --git a/chatbot/model_servers/common/Makefile.common b/chatbot/model_servers/common/Makefile.common new file mode 100644 index 0000000000000000000000000000000000000000..ae77d90e3c55e85c2f6a773115319151819a4a0c --- /dev/null +++ b/chatbot/model_servers/common/Makefile.common @@ -0,0 +1,57 @@ +CONTAINER_TOOL ?= podman +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +COMPONENT ?= model_servers +CHAT_FORMAT ?= + +BIND_MOUNT_OPTIONS := ro +OS := $(shell uname -s) +ifeq ($(OS),Linux) + BIND_MOUNT_OPTIONS := Z,ro +endif + +.PHONY: build +build: + podman build --squash-all --build-arg PORT=$(PORT) -t $(IMAGE) . -f base/Containerfile + +.PHONY: install +install: + pip install -r tests/requirements.txt + +.PHONY: test +test: + @if [ ! -f "../../models/$(MODEL_NAME)" ]; then \ + echo "Model file -- $(MODEL_NAME) -- not present in the models directory."; \ + exit 1; \ + else \ + if [ ! -f "./$(MODEL_NAME)" ]; then \ + ln -s ../../models/$(MODEL_NAME) ./$(MODEL_NAME); \ + fi; \ + REGISTRY=$(REGISTRY) IMAGE_NAME=$(IMAGE_NAME) MODEL_NAME=$(MODEL_NAME) MODEL_PATH=$(MODEL_PATH) PORT=$(PORT) pytest -vvv -s ; \ + fi; + +.PHONY: clean +clean: + - rm ./$(MODEL_NAME) &> /dev/null + +.PHONY: run +run: + cd ../../models && \ + podman run -it \ + -d \ + -p $(PORT):$(PORT) \ + -v ./$(MODEL_NAME):$(MODELS_PATH)/$(MODEL_NAME):$(BIND_MOUNT_OPTIONS) \ + -e MODEL_PATH=$(MODELS_PATH)/$(MODEL_NAME) \ + -e HOST=0.0.0.0 \ + -e PORT=$(PORT) \ + $(CHAT_FORMAT:%=-e CHAT_FORMAT=${CHAT_FORMAT}) \ + $(IMAGE) + +.PHONY: podman-clean +podman-clean: + @container_ids=$$(podman ps --format "{{.ID}} {{.Image}}" | awk '$$2 == "$(IMAGE)" {print $$1}'); \ + echo "removing all containers with IMAGE=$(IMAGE)"; \ + for id in $$container_ids; do \ + echo "Removing container: $$id,"; \ + podman rm -f $$id; \ + done diff --git a/chatbot/model_servers/llamacpp_python/Makefile b/chatbot/model_servers/llamacpp_python/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e74f7ea6ff4e1fec7bbd8f59d5eb6c522038f3db --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/Makefile @@ -0,0 +1,31 @@ +APP := llamacpp_python +PORT ?= 8001 +CHAT_FORMAT ?= + +include ../common/Makefile.common + +IMAGE_NAME ?= $(REGISTRY_ORG)/$(COMPONENT)/$(APP):latest +IMAGE := $(REGISTRY)/$(IMAGE_NAME) +CUDA_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/$(APP)_cuda:latest +VULKAN_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/$(APP)_vulkan:latest + +MODELS_PATH := /locallm/models +MODEL_NAME ?= granite-7b-lab-Q4_K_M.gguf + +.Phony: all +all: build download-model-granite run + +.PHONY: build-cuda +build-cuda: + "${CONTAINER_TOOL}" build --squash-all -t $(CUDA_IMAGE) . -f cuda/Containerfile + +.PHONY: build-vulkan-amd64 build-vulkan-arm64 +build-vulkan-amd64: + "${CONTAINER_TOOL}" build --squash-all -t $(VULKAN_IMAGE) . -f vulkan/amd64/Containerfile +build-vulkan-arm64: + "${CONTAINER_TOOL}" build --squash-all -t $(VULKAN_IMAGE) . -f vulkan/arm64/Containerfile + +.PHONY: download-model-granite # default model +download-model-granite: + cd ../../models/ && \ + make download-model-granite diff --git a/chatbot/model_servers/llamacpp_python/README.md b/chatbot/model_servers/llamacpp_python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..046b7fe42bb84babb8f83534d2a2887322db6442 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/README.md @@ -0,0 +1,177 @@ +# Llamacpp_Python Model Server + +The llamacpp_python model server images are based on the [llama-cpp-python](https://github.com/abetlen/llama-cpp-python) project that provides python bindings for [llama.cpp](https://github.com/ggerganov/llama.cpp). This provides us with a python based and OpenAI API compatible model server that can run LLM's of various sizes locally across Linux, Windows or Mac. + +This model server requires models to be converted from their original format, typically a set of `*.bin` or `*.safetensor` files into a single GGUF formatted file. Many models are available in GGUF format already on [huggingface.co](https://huggingface.co). You can also use the [model converter utility](../../convert_models/) available in this repo to convert models yourself. + + +## Image Options + +We currently provide 3 options for the llamacpp_python model server: +* [Base](#base) +* [Cuda](#cuda) +* [Vulkan (experimental)](#vulkan-experimental) + +### Base + +The [base image](../llamacpp_python/base/Containerfile) is the standard image that works for both arm64 and amd64 environments. However, it does not includes any hardware acceleration and will run with CPU only. If you use the base image, make sure that your container runtime has sufficient resources to run the desired model(s). + +To build the base model service image: + +```bash +make -f Makefile build +``` +To pull the base model service image: + +```bash +podman pull quay.io/ai-lab/llamacpp_python +``` + + +### Cuda + +The [Cuda image](../llamacpp_python/cuda/Containerfile) include all the extra drivers necessary to run our model server with Nvidia GPUs. This will significant speed up the models response time over CPU only deployments. + +To Build the the Cuda variant image: +```bash +make -f Makefile build-cuda +``` + +To pull the base model service image: + +```bash +podman pull quay.io/ai-lab/llamacpp_python_cuda +``` + +**IMPORTANT!** + +To run the Cuda image with GPU acceleration, you need to install the correct [Cuda drivers](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#driver-installation) for your system along with the [Nvidia Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html#). Please use the links provided to find installation instructions for your system. + +Once those are installed you can use the container toolkit CLI to discover your Nvidia device(s). +```bash +sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml +``` + +Finally, you will also need to add `--device nvidia.com/gpu=all` to your `podman run` command so your container can access the GPU. + + +### Vulkan (experimental) + +The [Vulkan image](../llamacpp_python/vulkan/Containerfile) is experimental, but can be used for gaining partial GPU access on an M-series Mac, significantly speeding up model response time over a CPU only deployment. This image requires that your podman machine provider is "applehv" and that you use krunkit instead of vfkit. Since these tools are not currently supported by podman desktop this image will remain "experimental". + +To build the Vulkan model service variant image: + +| System Architecture | Command | +|---|---| +| amd64 | make -f Makefile build-vulkan-amd64 | +| arm64 | make -f Makefile build-vulkan-arm64 | + +To pull the base model service image: + +```bash +podman pull quay.io/ai-lab/llamacpp_python_vulkan +``` + + + +## Download Model(s) + +There are many models to choose from these days, most of which can be found on [huggingface.co](https://huggingface.co). In order to use a model with the llamacpp_python model server, it must be in GGUF format. You can either download pre-converted GGUF models directly or convert them yourself with the [model converter utility](../../convert_models/) available in this repo. + +A well performant Apache-2.0 licensed models that we recommend using if you are just getting started is +`granite-7b-lab`. You can use the link below to quickly download a quantized (smaller) GGUF version of this model for use with the llamacpp_python model server. + +Download URL: [https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf](https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf) + +Place all models in the [models](../../models/) directory. + +You can use this snippet below to download the default model: + +```bash +make -f Makefile download-model-granite +``` + +Or you can use the generic `download-models` target from the `/models` directory to download any model file from huggingface: + +```bash +cd ../../models +make MODEL_NAME= MODEL_URL= -f Makefile download-model +# EX: make MODEL_NAME=granite-7b-lab-Q4_K_M.gguf MODEL_URL=https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf -f Makefile download-model +``` + + +## Deploy Model Service + +### Single Model Service: + +To deploy the LLM server you must specify a volume mount `-v` where your models are stored on the host machine and the `MODEL_PATH` for your model of choice. The model_server is most easily deploy from calling the make command: `make -f Makefile run`. Of course as with all our make calls you can pass any number of the following variables: `REGISTRY`, `IMAGE_NAME`, `MODEL_NAME`, `MODEL_PATH`, and `PORT`. + +```bash +podman run --rm -it \ + -p 8001:8001 \ + -v Local/path/to/locallm/models:/locallm/models:ro \ + -e MODEL_PATH=models/granite-7b-lab-Q4_K_M.gguf + -e HOST=0.0.0.0 + -e PORT=8001 + -e MODEL_CHAT_FORMAT=openchat + llamacpp_python \ +``` + +or with Cuda image + +```bash +podman run --rm -it \ + --device nvidia.com/gpu=all + -p 8001:8001 \ + -v Local/path/to/locallm/models:/locallm/models:ro \ + -e MODEL_PATH=models/granite-7b-lab-Q4_K_M.gguf + -e HOST=0.0.0.0 + -e PORT=8001 + -e MODEL_CHAT_FORMAT=openchat + llamacpp_python \ +``` +### Multiple Model Service: + +To enable dynamic loading and unloading of different models present on your machine, you can start the model service with a `CONFIG_PATH` instead of a `MODEL_PATH`. + +Here is an example `models_config.json` with two model options. + +```json +{ + "host": "0.0.0.0", + "port": 8001, + "models": [ + { + "model": "models/granite-7b-lab-Q4_K_M.gguf", + "model_alias": "granite", + "chat_format": "openchat", + }, + { + "model": "models/merlinite-7b-lab-Q4_K_M.gguf", + "model_alias": "merlinite", + "chat_format": "openchat", + }, + + ] +} +``` + +Now run the container with the specified config file. + +```bash +podman run --rm -it -d \ + -p 8001:8001 \ + -v Local/path/to/locallm/models:/locallm/models:ro \ + -e CONFIG_PATH=models/ \ + llamacpp_python +``` + +### DEV environment + +The environment is implemented with devcontainer technology. + +Running tests + +```bash +make -f Makefile test +``` \ No newline at end of file diff --git a/chatbot/model_servers/llamacpp_python/base/Containerfile b/chatbot/model_servers/llamacpp_python/base/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..b5dd132fca93ccf023250cd07ec10644e6db9f1c --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/base/Containerfile @@ -0,0 +1,9 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /locallm +COPY src . +USER root +RUN dnf install -y gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ +USER 1001 +RUN CC="/opt/rh/gcc-toolset-13/root/usr/bin/gcc" CXX="/opt/rh/gcc-toolset-13/root/usr/bin/g++" pip install --no-cache-dir --verbose -r ./requirements.txt +EXPOSE 8001 +ENTRYPOINT [ "sh", "./run.sh" ] diff --git a/chatbot/model_servers/llamacpp_python/cuda/Containerfile b/chatbot/model_servers/llamacpp_python/cuda/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..f8259ed035015a7bf0cb10f5ea9be0be45ab6be3 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/cuda/Containerfile @@ -0,0 +1,10 @@ +FROM quay.io/opendatahub/workbench-images:cuda-ubi9-python-3.9-20231206 +USER root +RUN dnf install -y gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ +USER 1001 +WORKDIR /locallm +COPY src . +ENV CMAKE_ARGS="-DGGML_CUDA=on -DLLAMA_AVX2=OFF -DLLAMA_FMA=OFF -DLLAMA_F16C=OFF" +ENV FORCE_CMAKE=1 +RUN CC="/opt/rh/gcc-toolset-13/root/usr/bin/gcc" CXX="/opt/rh/gcc-toolset-13/root/usr/bin/g++" pip install --no-cache-dir -r ./requirements.txt +ENTRYPOINT [ "sh", "run.sh" ] diff --git a/chatbot/model_servers/llamacpp_python/src/requirements.txt b/chatbot/model_servers/llamacpp_python/src/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..c349578ce49fdbec8c630e5f935303965ba82f92 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/src/requirements.txt @@ -0,0 +1,3 @@ +llama-cpp-python[server]==0.2.90 +transformers==4.41.2 +pip==24.0 diff --git a/chatbot/model_servers/llamacpp_python/src/run.sh b/chatbot/model_servers/llamacpp_python/src/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..817b87ea71cca0a9dbc4989e7100289ef5527361 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/src/run.sh @@ -0,0 +1,27 @@ +#!/bin/bash +if [ ${CONFIG_PATH} ] || [[ ${MODEL_PATH} && ${CONFIG_PATH} ]]; then + python -m llama_cpp.server --config_file ${CONFIG_PATH} + exit 0 +fi + +if [ "${MODEL_HF_PRETRAINED_MODEL}" == "None" ]; then + MODEL_HF_PRETRAINED_MODEL="" +fi + +if [ ${MODEL_PATH} ]; then + python -m llama_cpp.server \ + --model ${MODEL_PATH} \ + --host ${HOST:=0.0.0.0} \ + --port ${PORT:=8001} \ + --n_gpu_layers ${GPU_LAYERS:=0} \ + --clip_model_path ${CLIP_MODEL_PATH:=None} \ + --chat_format ${MODEL_CHAT_FORMAT:=llama-2} \ + ${PRETRAINED_MODEL_PATH:=} \ + ${MODEL_HF_PRETRAINED_MODEL:+--hf_pretrained_model_name_or_path ${MODEL_HF_PRETRAINED_MODEL}} \ + --interrupt_requests ${INTERRUPT_REQUESTS:=False} + exit 0 +fi + +echo "Please set either a CONFIG_PATH or a MODEL_PATH" +exit 1 + diff --git a/chatbot/model_servers/llamacpp_python/tests/__init__.py b/chatbot/model_servers/llamacpp_python/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/model_servers/llamacpp_python/tests/conftest.py b/chatbot/model_servers/llamacpp_python/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..c1ba0381056da26c6e14dfb4bb3963cf6cabfb67 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/tests/conftest.py @@ -0,0 +1,60 @@ +import pytest_container +import os + +# For cuda, will add this to below Container: extra_launch_args=["--device", "nvidia.com/gpu=all"], +if not 'REGISTRY' in os.environ: + REGISTRY = 'ghcr.io' +else: + REGISTRY = os.environ['REGISTRY'] + +if not 'IMAGE_NAME' in os.environ: + IMAGE_NAME = 'containers/llamacpp_python:latest' +else: + IMAGE_NAME = os.environ['IMAGE_NAME'] + +if not 'MODEL_NAME' in os.environ: + MODEL_NAME = 'granite-7b-lab-Q4_K_M.gguf' +else: + MODEL_NAME = os.environ['MODEL_NAME'] + +if not 'MODEL_PATH' in os.environ: + MODEL_PATH = "/locallm/models" +else: + MODEL_PATH = os.environ['MODEL_PATH'] + +if not 'PORT' in os.environ: + PORT = 8001 +else: + PORT = os.environ['PORT'] + try: + PORT = int(PORT) + except: + PORT = 8001 + +MS = pytest_container.Container( + url=f"containers-storage:{os.environ['REGISTRY']}/{os.environ['IMAGE_NAME']}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path="{MODEL_PATH}/{MODEL_NAME}".format(MODEL_PATH=MODEL_PATH, MODEL_NAME=MODEL_NAME), + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": "{MODEL_PATH}/{MODEL_NAME}".format(MODEL_PATH=MODEL_PATH, MODEL_NAME=MODEL_NAME), + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/chatbot/model_servers/llamacpp_python/tests/requirements.txt b/chatbot/model_servers/llamacpp_python/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..22fc97f27976ea5aebcc75d9586703a5b78df887 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/chatbot/model_servers/llamacpp_python/tests/test_alive.py b/chatbot/model_servers/llamacpp_python/tests/test_alive.py new file mode 100644 index 0000000000000000000000000000000000000000..b44e5467a2b0cec991808e1e06c88598d07757db --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/tests/test_alive.py @@ -0,0 +1,13 @@ +import pytest_container +from .conftest import MS +import tenacity +import os + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip() diff --git a/chatbot/model_servers/llamacpp_python/tooling_options.ipynb b/chatbot/model_servers/llamacpp_python/tooling_options.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..411419020cdacd6783249ee9611b6638d2f5618e --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/tooling_options.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Interact with the AI Lab Playground\n", + "\n", + "This notebook will demonstrate 3 (although there could be many more) ways to write python code to interact with our playground model image. The goal here is to show that our approach allows for flexibility and user choice when it comes to which LLM framework they want to use for development. \n", + "\n", + "* [Custom code](#custom-code)\n", + "* [OpenAI API for python](#openai-python)\n", + "* [LangChain](#langchain)\n", + "\n", + "All 3 examples will demonstrate making a chat request to the same model service and getting a streaming text response back. \n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook assumes that the playground image is running locally. Once built, you can use the below to start the model service image. \n", + "\n", + "```bash\n", + "podman run -it -p 8000:8000 -v /locallm/models:/locallm/models:Z -e MODEL_PATH=models/mistral-7b-instruct-v0.2.Q4_K_M.gguf playground\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Code\n", + "\n", + "This code block indicates that we can fairly easily write a custom class that only relies on the python packages `json` and `requests` to interact with our model service." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Ah, an excellent question! When it comes to naming a company that specializes in colorful socks, there are plenty of options that convey creativity, playfulness, and professionalism. Here are some suggestions:\n", + "1. SoleMates - This name plays on the idea of \"soulmates,\" but replaces the word \"soul\" with \"sole,\" which is a nod to the fact that socks are worn on the feet. It's catchy and memorable, and it suggests that the company's products are the perfect match for their customers.\n", + "2. Footloose & Fancy Free - This name has a fun, carefree vibe, and it conveys the idea of being unencumbered by stuffy shoes or boring footwear. It also has a nice ring to it, with the alliteration of \"foot\" and \"free.\"\n", + "3. Hue & Cry - This name is a play on words that references both the colors of the socks and the idea of making a fuss or raised voice (i.e., crying). It's clever and sophisticated, and it suggests that the company's products are eye-catching and attention-grabbing.\n", + "4. Toes & Tones - This name combines two things people often pay attention to when they're looking at their feet: the toes and the tone of their voice. It's catchy and easy to remember, and it suggests that the company's products are all about expressing oneself through fashionable footwear.\n", + "5. Ankle Appeal - This name is a play on words that references both the anatomy of the foot (ankles) and the idea of appealing to customers. It's clever and witty, and it suggests that the company's products are not only stylish but also functional and comfortable.\n", + "6. Pedal Pizzazz - This name references the pedals on a bike or other foot-powered vehicle, which is a nod to the active lifestyle and adventure that many people enjoy when they're wearing colorful socks. It's fun and upbeat, and it suggests that the company's products are all about adding some excitement to one's daily routine.\n", + "7. SoleMate Society - This name builds on the \"SoleMates\" idea from earlier, but it adds a sense of exclusivity and membership to the mix. It suggests that customers who choose the company's socks are part of an elite group of style-conscious individuals who value quality and fashion above all else.\n", + "8. Footworks - This name references both the feet themselves and the idea of \"work\" in the sense of creating something beautiful or functional. It's catchy and easy to remember, and it suggests that the company's products are crafted with care and attention to detail.\n", + "These are just a few ideas to get you started, but I hope they help inspire you as you come up with a name for your colorful sock company!" + ] + } + ], + "source": [ + "import requests\n", + "import json\n", + "\n", + "class Chat:\n", + " def __init__(self, endpoint) -> None:\n", + " self.endpoint = endpoint\n", + " self.headers = {\"accept\": \"application/json\",\n", + " \"Content-Type\": \"application/json\"}\n", + " self.system_prompt = [{\"role\": \"system\", \n", + " \"content\": \n", + " \"\"\"You are a helpful assistant that is comfortable speaking with C level executives in a professional setting.\"\"\"}]\n", + " self.session = requests.Session()\n", + "\n", + " def ask(self, prompt):\n", + " self.system_prompt.append({\"role\":\"user\",\"content\":prompt})\n", + " data = {\"messages\": self.system_prompt,\n", + " \"stream\": True,\n", + " \"temperature\": 0.9\n", + " } \n", + " r = self.session.post(self.endpoint, headers=self.headers,json=data,stream=True)\n", + " reply = \"\"\n", + " for line in r.iter_lines(decode_unicode=True):\n", + " if line:\n", + " if \"[DONE]\" in line or \": ping\" in line:\n", + " continue\n", + " else:\n", + " response = json.loads(line[6:])[\"choices\"][0][\"delta\"]\n", + " if \"content\" in response.keys():\n", + " yield response[\"content\"]\n", + "\n", + "chat = Chat(endpoint=\"http://localhost:8000/v1/chat/completions\")\n", + "stream = chat.ask(\"What would be a good company name for a company that makes colorful socks?\")\n", + "for chunk in stream:\n", + " print(chunk, end=\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### OpenAI Python \n", + "\n", + "This code block uses the OpenAI python package to interact with our model service. Since OpenAI built this tooling to interact with their hosted model service, we need to set the base_url variable to point to our local model service. But otherwise, this code from their docs is left unchanged. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Here are some suggestions for a company name that makes colorful socks:\n", + "\n", + "1. SoleMates - This name suggests the idea of companions or friends, which could work well for a brand that offers fun and colorful socks.\n", + "2. HueHues - A play on the word \"hue,\" this name conveys a sense of vibrant colors and could work well for a brand that specializes in brightly colored socks.\n", + "3. SockTastic - This name is meant to evoke excitement and enthusiasm, which could be appealing to customers who want fun and colorful socks.\n", + "4. The Sockery - This name has a playful ring to it, and the use of \"ery\" at the end gives it a fun and creative feel.\n", + "5. ColoredSocks Co. - A straightforward name that gets straight to the point. It's simple yet memorable, and easy for customers to remember.\n", + "6. Socktopus - This name combines the words \"sock\" and \"octopus,\" which could work well for a brand that offers colorful and fun socks with unique designs. \n", + "7. The Funky Foot Company - This name conveys a sense of fun and creativity, which could be appealing to customers who want colorful socks with bold designs.\n", + "8. ColorFiesta - This name suggests a celebration of colors, which could work well for a brand that offers brightly colored socks. \n", + "9. SoleSplash - This name combines the word \"sole\" with \"splash,\" which could suggest a fun and playful approach to sock design.\n", + "10. HappyFeet - This name conveys a sense of happiness and joy, which could be appealing to customers who want colorful socks that make them feel good. " + ] + } + ], + "source": [ + "# Example code from https://platform.openai.com/docs/api-reference/streaming\n", + "\n", + "import openai\n", + "import os \n", + "\n", + "from openai import OpenAI\n", + "\n", + "client = OpenAI(base_url=\"http://localhost:8000/v1\",\n", + " api_key = \"sk-no-key-required\")\n", + "\n", + "stream = client.chat.completions.create(\n", + " model=\"gpt-400\",\n", + " messages=[{\"role\": \"user\", \"content\": \"What would be a good company name for a company that makes colorful socks?\"}],\n", + " stream=True,\n", + ")\n", + "\n", + "for chunk in stream:\n", + " if chunk.choices[0].delta.content is not None:\n", + " print(chunk.choices[0].delta.content, end=\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Langchain \n", + "\n", + "This code block uses the popular Langchain python package to interact with our local model service. Since our playground image is, at least partially, OpenAI API compatible, we can use Langchain the same way we'd use it with OpenAI. Like above, we simply change the base URL to point to our hosted model. " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "I'm looking for something catchy, creative and eye-catching. I want it to stand out and make people want to buy them!\n", + "\n", + "What are some suggestions?\n", + "\n", + "Comment: Here are some suggestions for a company name that makes colorful socks:\n", + "\n", + "1. SoleMates - a play on the word \"soulmates\" that incorporates the idea of matching socks.\n", + "2. Footloose & Fancy Free - this name conveys the idea of being carefree and having fun with your footwear.\n", + "3. Hue & Cry - a play on words that references both color and emotion.\n", + "4. Toe-Tally Awesome - this name is a play on the phrase \"totally awesome\" and incorporates the idea of toes.\n", + "5. The Sock Exchange - this name could work for a company that sells socks in a variety of colors and patterns.\n", + "6. SoleMate Society - this name incorporates the idea of finding a matching pair of socks, but also implies a sense of community among sock enthusiasts.\n", + "7. The Happy Feet Company - this name conveys the idea" + ] + }, + { + "data": { + "text/plain": [ + "'\\n\\nI\\'m looking for something catchy, creative and eye-catching. I want it to stand out and make people want to buy them!\\n\\nWhat are some suggestions?\\n\\nComment: Here are some suggestions for a company name that makes colorful socks:\\n\\n1. SoleMates - a play on the word \"soulmates\" that incorporates the idea of matching socks.\\n2. Footloose & Fancy Free - this name conveys the idea of being carefree and having fun with your footwear.\\n3. Hue & Cry - a play on words that references both color and emotion.\\n4. Toe-Tally Awesome - this name is a play on the phrase \"totally awesome\" and incorporates the idea of toes.\\n5. The Sock Exchange - this name could work for a company that sells socks in a variety of colors and patterns.\\n6. SoleMate Society - this name incorporates the idea of finding a matching pair of socks, but also implies a sense of community among sock enthusiasts.\\n7. The Happy Feet Company - this name conveys the idea'" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example code from https://github.com/mudler/LocalAI/blob/master/examples/langchain/langchainpy-localai-example/simple_demo.py\n", + "\n", + "from langchain.llms import OpenAI\n", + "from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler\n", + "\n", + "\n", + "llm = OpenAI(temperature=0.9,model_name=\"instructlab/granite-7b-lab\", base_url=\"http://localhost:8000/v1\", \n", + " openai_api_key=\"sk-no-key-required\", streaming=True,\n", + " callbacks=[StreamingStdOutCallbackHandler()])\n", + "text = \"What would be a good company name for a company that makes colorful socks?\"\n", + "llm(text)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "locallm", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/chatbot/model_servers/llamacpp_python/vulkan/amd64/Containerfile b/chatbot/model_servers/llamacpp_python/vulkan/amd64/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..eb30ae11ae5065fae17e6fc036163463b5f7da25 --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/vulkan/amd64/Containerfile @@ -0,0 +1,17 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +USER 0 +RUN dnf install -y python3-dnf-plugin-versionlock +RUN dnf install -y mesa-vulkan-drivers-23.3.3-1.el9.x86_64 +RUN dnf versionlock mesa-vulkan-drivers-23.3.3-1.el9.x86_64 +RUN dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm +RUN dnf install -y git cmake ninja-build gcc gcc-c++ +RUN dnf copr enable -y ligenix/enterprise-sandbox epel-9-x86_64 +RUN dnf install -y vulkan-headers vulkan-tools +USER 1001 +WORKDIR /locallm +COPY src . +RUN pip install --upgrade pip +ENV CMAKE_ARGS="-DLLAMA_VULKAN=on" +ENV FORCE_CMAKE=1 +RUN pip install --no-cache-dir --upgrade -r /locallm/requirements.txt +ENTRYPOINT [ "sh", "run.sh" ] diff --git a/chatbot/model_servers/llamacpp_python/vulkan/arm64/Containerfile b/chatbot/model_servers/llamacpp_python/vulkan/arm64/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..eb371ff5cc759e3d305e8b944a18b5bfd114854b --- /dev/null +++ b/chatbot/model_servers/llamacpp_python/vulkan/arm64/Containerfile @@ -0,0 +1,17 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +USER 0 +RUN dnf install -y python3-dnf-plugin-versionlock && \ + dnf install -y \ +https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && \ + dnf copr enable -y slp/mesa-krunkit epel-9-aarch64 && \ + dnf install -y mesa-vulkan-drivers-23.3.3-101.el9.aarch64 && \ + dnf versionlock mesa-vulkan-drivers-23.3.3-101.el9.aarch64 && \ + dnf install -y git cmake ninja-build gcc gcc-c++ vulkan-loader-devel vulkan-tools +USER 1001 +WORKDIR /locallm +COPY src . +RUN pip install --upgrade pip +ENV CMAKE_ARGS="-DLLAMA_VULKAN=on" +ENV FORCE_CMAKE=1 +RUN pip install --no-cache-dir --upgrade -r /locallm/requirements.txt +ENTRYPOINT [ "sh", "run.sh" ] diff --git a/chatbot/model_servers/object_detection_python/Makefile b/chatbot/model_servers/object_detection_python/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c4e2f87615f6e3010c86704dec2138a11907659e --- /dev/null +++ b/chatbot/model_servers/object_detection_python/Makefile @@ -0,0 +1,33 @@ +APP := object_detection_python +PORT ?= 8000 + +REGISTRY ?= ghcr.io +REGISTRY_ORG ?= containers + +MODEL_NAME ?= facebook/detr-resnet-101 +MODELS_DIR := /app/models + +include ../common/Makefile.common + +IMAGE_NAME ?= $(REGISTRY_ORG)/$(APP):latest +IMAGE := $(REGISTRY)/$(IMAGE_NAME) + +# Run override required because of the multi-directory models and model_path vs models_dir +.PHONY: run +run: + cd ../../models && \ + podman run -it -d -p $(PORT):$(PORT) -v ./$(MODEL_NAME):$(MODELS_DIR)/$(MODEL_NAME):$(BIND_MOUNT_OPTIONS) -e MODEL_PATH=$(MODELS_DIR)/$(MODEL_NAME) -e HOST=0.0.0.0 -e PORT=$(PORT) $(IMAGE) + +.PHONY: all +all: build download-model-facebook-detr-resnet-101 run + +.PHONY: download-model-facebook-detr-resnet-101 +download-model-facebook-detr-resnet-101: + cd ../../models && \ + make download-model-facebook-detr-resnet-101 + +.PHONY: test +test: + pip install -r ../../convert_models/requirements.txt + cp -r ../../models/facebook ./ + REGISTRY=$(REGISTRY) MODEL_NAME=$(MODEL_NAME) MODELS_DIR=$(MODELS_DIR) IMAGE_NAME=$(IMAGE_NAME) PORT=$(PORT) pytest -s -vvv diff --git a/chatbot/model_servers/object_detection_python/README.md b/chatbot/model_servers/object_detection_python/README.md new file mode 100644 index 0000000000000000000000000000000000000000..24fe41673438704d523cd0642110237399fbbeaf --- /dev/null +++ b/chatbot/model_servers/object_detection_python/README.md @@ -0,0 +1,52 @@ +# Object_Detection_Python Model Server + +The object_detection_python model server is a simple [FastAPI](https://fastapi.tiangolo.com/) application written specifically for use in the [object_detection recipe](../../recipes/computer_vision/object_detection/) with "DEtection TRansformer" (DETR) models. It relies on huggingface's transformer package for `AutoImageProcessor` and `AutoModelforObjectDetection` to process image data and to make inferences respectively. + +Currently, the server only implements a single endpoint, `/detection`, that expects an image in bytes and returns an image with labeled bounding boxes and the probability scores of each bounding box. + +## Build Model Server + +To build the object_detection_python model server image from this directory: + +```bash +podman build -t object_detection_python . base/Containerfile +``` +or +```bash +make build +``` + +## Download Model(s) + +You can download models from [huggingface.co](https://huggingface.co/) for this model server. This model server is intended to be used with "DEtection TRansformer" (DETR) models. The default model we've used and validated is [facebook/detr-resnet-101](https://huggingface.co/facebook/detr-resnet-101). + +You can download a copy of this model into your `models/` with the make command below. + +```bash + make download-model-facebook-detr-resnet-101 +``` +or any model with + +```bash +cd ../../models/ && \ +python download_hf_models.py -m +``` + + +## Deploy Model Server + +The model server relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your model server using the following `make` command from the [`model_servers/object_detection_python`](../../../model_servers/object_detection_python) directory, which will be set with reasonable defaults: + +```bash +make run +``` +or + +```bash +podman run -it -d -p 8000:8000 \ +-v /facebook/detr-resnet-101:/models/facebook/detr-resnet-101:ro \ +-e MODEL_PATH=/models/facebook/detr-resnet-101 \ +-e HOST=0.0.0.0 \ +-e PORT=8000 \ +ghcr.io/ai-lab/model_servers/object_detection_python:latest +``` diff --git a/chatbot/model_servers/object_detection_python/base/Containerfile b/chatbot/model_servers/object_detection_python/base/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..314d45a8c265cac4056fd3ad8f46452f017011f6 --- /dev/null +++ b/chatbot/model_servers/object_detection_python/base/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +ARG PORT=8000 +WORKDIR /app +COPY src . +RUN pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade -r requirements.txt +EXPOSE $PORT +ENTRYPOINT [ "sh", "./run.sh" ] diff --git a/chatbot/model_servers/object_detection_python/src/object_detection_server.py b/chatbot/model_servers/object_detection_python/src/object_detection_server.py new file mode 100644 index 0000000000000000000000000000000000000000..0f8a1e996171173a6eb7dcf1530ef3e93e99bdf7 --- /dev/null +++ b/chatbot/model_servers/object_detection_python/src/object_detection_server.py @@ -0,0 +1,61 @@ +from transformers import AutoImageProcessor, AutoModelForObjectDetection +from huggingface_hub import snapshot_download +from PIL import Image, ImageDraw +from fastapi import FastAPI +from pydantic import BaseModel +import torch +import base64 +import os +import io +import shutil + + +app = FastAPI() +model = os.getenv("MODEL_PATH", default="/app/models/facebook/detr-resnet-101") +revision = os.getenv("MODEL_REVISION", default="no_timm") + +if os.path.isfile(model): + model_name = os.getenv("MODEL_NAME", default="facebook/detr-resnet-101") + snapshot_download(repo_id=model_name, + revision=revision, + local_dir=f"/tmp/{model}", + local_dir_use_symlinks=False) + shutil.copyfile(model, f"/tmp/{model}/pytorch_model.bin") + processor = AutoImageProcessor.from_pretrained(f"/tmp/{model}", revision=revision) + model = AutoModelForObjectDetection.from_pretrained(f"/tmp/{model}", revision=revision) +else: + processor = AutoImageProcessor.from_pretrained(model, revision=revision) + model = AutoModelForObjectDetection.from_pretrained(model, revision=revision) + +class Item(BaseModel): + image: bytes + +@app.get("/health") +def tests_alive(): + return {"alive": True} + +@app.post("/detection") +def detection(item: Item): + b64_image = item.image + b64_image = base64.b64decode(b64_image) + bytes_io = io.BytesIO(b64_image) + image = Image.open(bytes_io) + inputs = processor(images=image, return_tensors="pt") + outputs = model(**inputs) + target_sizes = torch.tensor([image.size[::-1]]) + results = processor.post_process_object_detection(outputs, target_sizes=target_sizes, threshold=0.9)[0] + draw = ImageDraw.Draw(image) + scores = [] + for score, label, box in zip(results["scores"], results["labels"], results["boxes"]): + box = [round(i, 2) for i in box.tolist()] + x, y, x2, y2 = tuple(box) + draw.rectangle((x, y, x2, y2), outline="red", width=1) + draw.text((x, y), model.config.id2label[label.item()], fill="white") + label_confidence = f"Detected {model.config.id2label[label.item()]} with confidence {round(score.item(), 3)}" + scores.append(label_confidence) + + bytes_io = io.BytesIO() + image.save(bytes_io, "JPEG") + img_bytes = bytes_io.getvalue() + img_bytes = base64.b64encode(img_bytes).decode('utf-8') + return {'image': img_bytes, "boxes": scores} \ No newline at end of file diff --git a/chatbot/model_servers/object_detection_python/src/requirements-unlocked.txt b/chatbot/model_servers/object_detection_python/src/requirements-unlocked.txt new file mode 100644 index 0000000000000000000000000000000000000000..7b2698d14d210d54fb08de5a70768f7f911ecbab --- /dev/null +++ b/chatbot/model_servers/object_detection_python/src/requirements-unlocked.txt @@ -0,0 +1,8 @@ +fastapi +pillow +pydantic +requests +transformers +torch +uvicorn +timm diff --git a/chatbot/model_servers/object_detection_python/src/requirements.txt b/chatbot/model_servers/object_detection_python/src/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..17fdbc95b12c6b0290f4058a93c309da45ef6919 --- /dev/null +++ b/chatbot/model_servers/object_detection_python/src/requirements.txt @@ -0,0 +1,55 @@ +annotated-types==0.7.0 +anyio==4.4.0 +certifi==2024.6.2 +charset-normalizer==3.3.2 +click==8.1.7 +dnspython==2.6.1 +email_validator==2.2.0 +fastapi==0.111.1 +fastapi-cli==0.0.5 +filelock==3.15.4 +fsspec==2024.6.1 +h11==0.14.0 +httpcore==1.0.6 +httptools==0.6.1 +httpx==0.27.2 +huggingface-hub==0.23.4 +idna==3.7 +Jinja2==3.1.4 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +mdurl==0.1.2 +mpmath==1.3.0 +networkx==3.3 +numpy==2.0.1 +orjson==3.10.7 +packaging==24.1 +pillow==10.3.0 +pydantic==2.7.4 +pydantic_core==2.18.4 +Pygments==2.18.0 +python-dotenv==1.0.1 +python-multipart==0.0.12 +PyYAML==6.0.2 +regex==2024.5.15 +requests==2.32.3 +rich==13.7.1 +safetensors==0.4.5 +shellingham==1.5.4 +sniffio==1.3.1 +starlette==0.37.2 +sympy==1.12.1 +timm==1.0.9 +tokenizers==0.19.1 +torch==2.3.1 +torchvision==0.18.1 +tqdm==4.66.5 +transformers==4.41.2 +typer==0.12.5 +typing_extensions==4.12.2 +ujson==5.10.0 +urllib3==2.2.3 +uvicorn==0.30.6 +uvloop==0.19.0 +watchfiles==0.22.0 +websockets==12.0 diff --git a/chatbot/model_servers/object_detection_python/src/run.sh b/chatbot/model_servers/object_detection_python/src/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..0267f48c74a97d2199389bc7aa3ff2e686b94bbc --- /dev/null +++ b/chatbot/model_servers/object_detection_python/src/run.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ ${MODEL_PATH} ]; then + PORT=${PORT} MODEL_PATH=${MODEL_PATH} uvicorn object_detection_server:app --port ${PORT:=8000} --host ${HOST:=0.0.0.0} + exit 0 +fi + +echo "Please set either a MODEL_PATH" +exit 1 diff --git a/chatbot/model_servers/object_detection_python/tests/__init__.py b/chatbot/model_servers/object_detection_python/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/model_servers/object_detection_python/tests/conftest.py b/chatbot/model_servers/object_detection_python/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..ad019c9dbee7234ca59d126580c2e01cb20882aa --- /dev/null +++ b/chatbot/model_servers/object_detection_python/tests/conftest.py @@ -0,0 +1,46 @@ +import pytest_container +import os + +REGISTRY = os.getenv("REGISTRY", "ghcr.io") +IMAGE_NAME = os.getenv("IMAGE_NAME", "containers/object_detection_python:latest") +MODEL_NAME = os.getenv("MODEL_NAME", "facebook/detr-resnet-101") +MODELS_DIR = os.getenv("MODELS_DIR", "/app/models") + +MODEL_PATH = f"{MODELS_DIR}/{MODEL_NAME}" + +PORT = os.getenv("PORT", 8000) +if type(PORT) == str: + try: + PORT = int(PORT) + except: + PORT = 8000 + +MS = pytest_container.Container( + url=f"containers-storage:{REGISTRY}/{IMAGE_NAME}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"{MODEL_PATH}", + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": f"{MODEL_PATH}", + "HOST": "0.0.0.0", + "PORT": f"{PORT}", + "IMAGE_NAME": f"{IMAGE_NAME}", + "REGISTRY": f"{REGISTRY}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/chatbot/model_servers/object_detection_python/tests/requirements.txt b/chatbot/model_servers/object_detection_python/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..22fc97f27976ea5aebcc75d9586703a5b78df887 --- /dev/null +++ b/chatbot/model_servers/object_detection_python/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/chatbot/model_servers/object_detection_python/tests/test_alive.py b/chatbot/model_servers/object_detection_python/tests/test_alive.py new file mode 100644 index 0000000000000000000000000000000000000000..226aac1c0029ac3c73d9b798d8ef9be704fb9198 --- /dev/null +++ b/chatbot/model_servers/object_detection_python/tests/test_alive.py @@ -0,0 +1,12 @@ +import pytest_container +from .conftest import MS +import tenacity + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip() diff --git a/chatbot/model_servers/ollama/README.md b/chatbot/model_servers/ollama/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a8ed64a16e45db2ef994abb6de4f0fbe06131be2 --- /dev/null +++ b/chatbot/model_servers/ollama/README.md @@ -0,0 +1,46 @@ +# Ollama 🦙 Model Server + +We can also use the official [Ollama](https://ollama.ai) image for our model service, [ollama/ollama:latest](https://hub.docker.com/r/ollama/ollama). + +### Download a Model + +Use the ollama cli to download a model to your host machine. For example, to download Mistral-7B use the command below. + +```bash +ollama pull mistral +``` + +### Run the Model Server + +Run the model server and create a volume mount into your localhost where the ollama models are stored. +```bash +# Notes: $HOME/.ollama is the default ollama installation directory, but this may be different per person +podman run -d -it --rm -v $HOME/.ollama:/root/.ollama:Z -p 11434:11434 ollama/ollama +``` + +### Interact with your models + +Once your service is up and running it will expose a REST API that you can use to interact with your models. + +```bash +curl -X POST http://localhost:11434/api/generate -d '{ + "model": "mistral", + "prompt":"Why is the sky blue?" + }' +``` +Please note that the ollama API is slightly different from the llamacpp_python API used throughout this repository. Be sure to make any appropriate changes to application code to accommodate these differences. + +The full API docs for the ollama model server can be found here: https://github.com/ollama/ollama/blob/main/docs/api.md + +However, the ollama model server **does** currently has partial OpenAI API compatibility and should work with any tool that calls `/v1/chat/completions` + +```python +from langchain_openai import ChatOpenAI +llm = ChatOpenAI(base_url="http://localhost:11434/v1", + api_key="sk-no-key-required", + model="mistral") +llm.invoke("hello") +``` +### Model Swapping + +Unlike the llamacpp_python server, ollama requires a `model` parameter to be passed on every call to the Model Service. This instructs the server to load (or keep in place) the model named. This allows users to more easily switch between the models stored on their machines. \ No newline at end of file diff --git a/chatbot/model_servers/ollama/base/Containerfile b/chatbot/model_servers/ollama/base/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..39280b94d47016ea1d1de8fef884d2c6b5b358a2 --- /dev/null +++ b/chatbot/model_servers/ollama/base/Containerfile @@ -0,0 +1 @@ +FROM ollama/ollama diff --git a/chatbot/model_servers/whispercpp/Makefile b/chatbot/model_servers/whispercpp/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f639c8f3bc33f68a71f0e26fd8c0fbed4f7328e --- /dev/null +++ b/chatbot/model_servers/whispercpp/Makefile @@ -0,0 +1,20 @@ +APP := whispercpp +PORT ?= 8001 + +include ../common/Makefile.common + +IMAGE_NAME ?= $(REGISTRY_ORG)/$(COMPONENT)/$(APP):latest +IMAGE ?= $(REGISTRY)/$(IMAGE_NAME) +# CUDA_IMAGE_NAME := $(REGISTRY)/$(BASE_IMAGE_NAME)/$(APP)_cuda:latest +# VULKAN_IMAGE := $(REGISTRY)/$(BASE_IMAGE_NAME)/$(APP)_vulkan:latest + +MODELS_PATH := /app/models +MODEL_NAME ?= ggml-small.bin + +.PHONY: all +all: build download-model-whisper-small run + +.PHONY: download-model-whisper-small +download-model-whisper-small: + cd ../../models && \ + make download-model-whisper-small diff --git a/chatbot/model_servers/whispercpp/README.md b/chatbot/model_servers/whispercpp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5abd6cfa398666f3021eaed4cb78f6e05baf526c --- /dev/null +++ b/chatbot/model_servers/whispercpp/README.md @@ -0,0 +1,58 @@ +## Whisper + +Whisper models are useful for converting audio files to text. The sample application [audio-to-text](../audio-to-text/README.md) +describes how to run an inference application. This document describes how to build a service for a Whisper model. + +### Build model service + +To build a Whisper model service container image from this directory, + +```bash +podman build -t whisper:image . +``` +or + +```bash +make -f Makefile build +``` + +### Download Whisper model + +You can to download the model from HuggingFace. There are various Whisper models available which vary in size and can be found +[here](https://huggingface.co/ggerganov/whisper.cpp). We will be using the `small` model which is about 466 MB. + +- **small** + - Download URL: [https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin) + +- **base.en** + - Download URL: [https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin](https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin) + +```bash +cd ../../models +curl -sLO +cd ../model_servers/whispercpp +``` + +### Deploy Model Service + +Deploy the LLM and volume mount the model of choice. +Here, we are mounting the `ggml-small.bin` model as downloaded from above. + +```bash +# Note: the :Z may need to be omitted from the model volume mount if not running on Linux + +podman run --rm -it \ + -p 8001:8001 \ + -v /local/path/to/locallm/models/ggml-small.bin:/models/ggml-small.bin:Z,ro \ + -e HOST=0.0.0.0 \ + -e MODEL_PATH=/models/ggml-small.bin \ + -e PORT=8001 \ + whisper:image +``` + +or using the make command: + +`make -f Makefile run` + +By default, a sample `jfk.wav` file is included in the whisper image. This can be used to test with. +The environment variable `AUDIO_FILE`, can be passed with your own audio file to override the default `/app/jfk.wav` file within the whisper image. diff --git a/chatbot/model_servers/whispercpp/base/Containerfile b/chatbot/model_servers/whispercpp/base/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..708131fedb7b63ccf038560f98086cd9a6e5eb91 --- /dev/null +++ b/chatbot/model_servers/whispercpp/base/Containerfile @@ -0,0 +1,28 @@ +FROM registry.access.redhat.com/ubi9/ubi:latest as builder + +WORKDIR /app +RUN dnf install -y git make gcc gcc-c++ +RUN mkdir whisper && cd whisper && git clone https://github.com/ggerganov/whisper.cpp.git . && \ + git checkout tags/v1.5.4 && \ + make && \ + cp main /app/main && \ + cp server /app/server && \ + cp samples/jfk.wav /app/jfk.wav && \ + cd ../ && rm -rf whisper + +FROM registry.access.redhat.com/ubi9-minimal:latest +WORKDIR /app +COPY --from=builder /app /app + +# https://github.com/wader/static-ffmpeg +# https://hub.docker.com/r/mwader/static-ffmpeg/ +COPY --from=mwader/static-ffmpeg:6.1.1 /ffmpeg /bin/ +COPY --from=mwader/static-ffmpeg:6.1.1 /ffprobe /bin/ + +COPY --chown=0:0 --chmod=755 src /app +RUN chown 1001:1001 /app + +USER 1001 + +ENV AUDIO_FILE=/app/jfk.wav +ENTRYPOINT ["sh", "run.sh"] diff --git a/chatbot/model_servers/whispercpp/src/run.sh b/chatbot/model_servers/whispercpp/src/run.sh new file mode 100644 index 0000000000000000000000000000000000000000..505de8dbf295f79b2604b4971215e94c992fb2e8 --- /dev/null +++ b/chatbot/model_servers/whispercpp/src/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./server -tr --model ${MODEL_PATH} --convert --host ${HOST:=0.0.0.0} --port ${PORT:=8001} diff --git a/chatbot/model_servers/whispercpp/tests/__init__.py b/chatbot/model_servers/whispercpp/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/model_servers/whispercpp/tests/conftest.py b/chatbot/model_servers/whispercpp/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..3d8159ac75206ae97078688912e621f62774dd1d --- /dev/null +++ b/chatbot/model_servers/whispercpp/tests/conftest.py @@ -0,0 +1,59 @@ +import pytest_container +import os + +if not 'REGISTRY' in os.environ: + REGISTRY = 'ghcr.io' +else: + REGISTRY = os.environ['REGISTRY'] + +if not 'IMAGE_NAME' in os.environ: + IMAGE_NAME = 'containers/whispercpp:latest' +else: + IMAGE_NAME = os.environ['IMAGE_NAME'] + +if not 'MODEL_NAME' in os.environ: + MODEL_NAME = 'ggml-small.bin' +else: + MODEL_NAME = os.environ['MODEL_NAME'] + +if not 'MODEL_PATH' in os.environ: + MODEL_PATH = "/app/models" +else: + MODEL_PATH = os.environ['MODEL_PATH'] + +if not 'PORT' in os.environ: + PORT = 8001 +else: + PORT = os.environ['PORT'] + try: + PORT = int(PORT) + except: + PORT = 8001 + +MS = pytest_container.Container( + url=f"containers-storage:{REGISTRY}/{IMAGE_NAME}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path="{MODEL_PATH}/{MODEL_NAME}".format(MODEL_PATH=MODEL_PATH, MODEL_NAME=MODEL_NAME), + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": "{MODEL_PATH}/{MODEL_NAME}".format(MODEL_PATH=MODEL_PATH, MODEL_NAME=MODEL_NAME), + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/chatbot/model_servers/whispercpp/tests/requirements.txt b/chatbot/model_servers/whispercpp/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..22fc97f27976ea5aebcc75d9586703a5b78df887 --- /dev/null +++ b/chatbot/model_servers/whispercpp/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/chatbot/model_servers/whispercpp/tests/test_alive.py b/chatbot/model_servers/whispercpp/tests/test_alive.py new file mode 100644 index 0000000000000000000000000000000000000000..226aac1c0029ac3c73d9b798d8ef9be704fb9198 --- /dev/null +++ b/chatbot/model_servers/whispercpp/tests/test_alive.py @@ -0,0 +1,12 @@ +import pytest_container +from .conftest import MS +import tenacity + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip() diff --git a/chatbot/models/Containerfile b/chatbot/models/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..5299b88c00f4b9fa9e63290c06ae77b1c6062f9f --- /dev/null +++ b/chatbot/models/Containerfile @@ -0,0 +1,18 @@ +# Suggested alternative open AI Models +# https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf (Default) +# https://huggingface.co/instructlab/merlinite-7b-lab-GGUF/resolve/main/merlinite-7b-lab-Q4_K_M.gguf +# https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_S.gguf +# https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf +# https://huggingface.co/TheBloke/CodeLlama-7B-Instruct-GGUF/resolve/main/codellama-7b-instruct.Q4_K_M.gguf +# https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin +# podman build --build-arg="MODEL_URL=https://..." -t quay.io/yourimage . +# +FROM registry.access.redhat.com/ubi9/ubi-micro:9.4-15 + +# Can be substituted using the --build-arg defined above +ARG MODEL_URL=https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf + +# By default the Model Server container image uses the AI Model stored in the model/model.file file. +WORKDIR /model + +ADD $MODEL_URL /model/model.file diff --git a/chatbot/models/Makefile b/chatbot/models/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9ca8778f3bf155c6317a819b04d5edbfdd837fb3 --- /dev/null +++ b/chatbot/models/Makefile @@ -0,0 +1,51 @@ +CONTAINER_TOOL ?= podman +MODEL_URL ?= https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +MODEL_NAME ?= granite-7b-lab-Q4_K_M.gguf + +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +COMPONENT = models + +IMAGE ?= $(shell tr '[:upper:]' '[:lower:]' <<< $(REGISTRY)/$(REGISTRY_ORG)/$(MODEL_NAME):latest) + +.PHONY: build +build: + "${CONTAINER_TOOL}" build $(MODEL_URL:%=--build-arg MODEL_URL=%) -f Containerfile -t ${IMAGE} . + +.PHONY: download-model +download-model: + curl -H "Cache-Control: no-cache" --max-time 900 --retry 2 --retry-delay 15 --connect-timeout 180 --progress-bar -S -L -f $(MODEL_URL) -z $(MODEL_NAME) -o $(MODEL_NAME).tmp && \ + mv -f $(MODEL_NAME).tmp $(MODEL_NAME) 2>/dev/null || \ + rm -f $(MODEL_NAME).tmp $(MODEL_NAME) + +.PHONY: download-model-granite +download-model-granite: + $(MAKE) MODEL_URL=https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf MODEL_NAME=granite-7b-lab-Q4_K_M.gguf download-model + +.PHONY: download-model-merlinite +download-model-merlinite: + $(MAKE) MODEL_URL=https://huggingface.co/instructlab/merlinite-7b-lab-GGUF/resolve/main/merlinite-7b-lab-Q4_K_M.gguf MODEL_NAME=merlinite-7b-lab-Q4_K_M.gguf download-model + +.PHONY: download-model-whisper-small +download-model-whisper-small: + $(MAKE) MODEL_NAME=ggml-small.bin MODEL_URL=https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin download-model + +.PHONY: download-model-mistral +download-model-mistral: + $(MAKE) MODEL_NAME=mistral-7b-instruct-v0.2.Q4_K_M.gguf MODEL_URL=https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf download-model + +.PHONY: download-model-mistral-code +download-model-mistral-code: + $(MAKE) MODEL_NAME=mistral-7b-code-16k-qlora.Q4_K_M.gguf MODEL_URL=https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF/resolve/main/mistral-7b-code-16k-qlora.Q4_K_M.gguf download-model + +.PHONY: download-model-facebook-detr-resnet-101 +download-model-facebook-detr-resnet-101: + python3 -m pip install -r ../convert_models/requirements.txt + cd ../convert_models/ && \ + python3 download_huggingface.py -m facebook/detr-resnet-101 + cp -r ../convert_models/converted_models/facebook ./ + +.PHONY: clean +clean: + -rm -f *tmp + -rm -f mistral* ggml-* granite* merlinite* diff --git a/chatbot/models/README.md b/chatbot/models/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f2ecaf9f880e832fd43e052978e0845305e2ff17 --- /dev/null +++ b/chatbot/models/README.md @@ -0,0 +1,19 @@ +# Directory to store model files + +The models directory stores models and provides automation around downloading models. + +Want to try one of our tested models? Try one or all of the following: + +```bash +make download-model-granite +make download-model-merlinite +make download-model-mistral +make download-model-mistral-code +make download-model-whisper-small +``` + +Want to download and run a model you don't see listed? This is supported with the `MODEL_NAME` and `MODEL_URL` params: + +```bash +make download-model MODEL_URL=https://huggingface.co/TheBloke/openchat-3.5-0106-GGUF/resolve/main/openchat-3.5-0106.Q4_K_M.gguf MODEL_NAME=openchat-3.5-0106.Q4_K_M.gguf +``` diff --git a/chatbot/models/download_hf_models.py b/chatbot/models/download_hf_models.py new file mode 100644 index 0000000000000000000000000000000000000000..37e69e1314952af5500a3c9c7bb51754552f2071 --- /dev/null +++ b/chatbot/models/download_hf_models.py @@ -0,0 +1,44 @@ +from huggingface_hub import snapshot_download, hf_hub_download ,HfFileSystem +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("-m", "--model") +parser.add_argument("-o", "--output", default="./models") +parser.add_argument("-q", "--quantization", default="Q4_K_M") +args = parser.parse_args() + +gguf = False +safetensor = False +ignore_patterns = ["*.md", ".gitattributes"] + +fs = HfFileSystem() +files = fs.ls(args.model, detail=False) + +for f in files: + if ".gguf" in f: + gguf = True + break + if ".safetensor" in f: + safetensor = True + break + +if gguf: + file_name = [x for x in files if args.quantization in x][0] + file_name_parts = file_name.split("/") + local_dir = f"{args.output}/{file_name_parts[1]}" + hf_hub_download(repo_id=f"{file_name_parts[0]}/{file_name_parts[1]}", + filename=file_name_parts[2], + local_dir=local_dir, + local_dir_use_symlinks=False + ) +else: + if safetensor: + ignore_patterns.append("*.bin") + + file_name_parts = args.model.split("/") + local_dir = f"{args.output}/{file_name_parts[1]}" + snapshot_download(repo_id=args.model, + local_dir=local_dir, + local_dir_use_symlinks=False, + ignore_patterns=ignore_patterns, + ) \ No newline at end of file diff --git a/chatbot/recipes/audio/audio_to_text/Makefile b/chatbot/recipes/audio/audio_to_text/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..e058e7f5ec4ef51a85e76e05bfac450ad5e3d82d --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= audio-to-text +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/audio/audio_to_text/README.md b/chatbot/recipes/audio/audio_to_text/README.md new file mode 100644 index 0000000000000000000000000000000000000000..ebc5cdfefa5ce60efbeaffdbd5478b5ec54f7fc1 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/README.md @@ -0,0 +1,100 @@ +# Audio to Text Application + +This recipe helps developers start building their own custom AI enabled audio transcription applications. It consists of two main components: the Model Service and the AI Application. + +There are a few options today for local Model Serving, but this recipe will use [`whisper-cpp`](https://github.com/ggerganov/whisper.cpp.git) and its included Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/whispercpp/base/Containerfile`](/model_servers/whispercpp/base/Containerfile). + +The AI Application will connect to the Model Service via an API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the audio to text application below. + + +![](/assets/whisper.png) + +## Try the Audio to Text Application: + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Audio to Text` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will go into greater detail on how each container in the application above is built, run, and what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) + * [Input audio files](#input-audio-files) + +## Download a model + +If you are just getting started, we recommend using [ggerganov/whisper.cpp](https://huggingface.co/ggerganov/whisper.cpp). +This is a well performant model with an MIT license. +It's simple to download a pre-converted whisper model from [huggingface.co](https://huggingface.co) +here: https://huggingface.co/ggerganov/whisper.cpp. There are a number of options, but we recommend to start with `ggml-small.bin`. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin +cd ../recipes/audio/audio_to_text +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the [whispercpp model-service document](../../../model_servers/whispercpp/README.md). + +```bash +# from path model_servers/whispercpp from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/whispercpp/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/whispercpp` set with reasonable defaults: + +```bash +# from path model_servers/whispercpp from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +Now that the Model Service is running we want to build and deploy our AI Application. Use the provided Containerfile to build the AI Application +image from the [`audio-to-text/`](./) directory. + +```bash +# from path recipes/audio/audio_to_text from repo containers/ai-lab-recipes +podman build -t audio-to-text app +``` +### Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. +When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. +This could be any appropriately hosted Model Service (running locally or in the cloud) using a compatible API. +The following Podman command can be used to run your AI Application: + +```bash +podman run --rm -it -p 8501:8501 -e MODEL_ENDPOINT=http://10.88.0.1:8001/inference audio-to-text +``` + +### Interact with the AI Application + +Once the streamlit application is up and running, you should be able to access it at `http://localhost:8501`. +From here, you can upload audio files from your local machine and translate the audio files as shown below. + +By using this recipe and getting this starting point established, +users should now have an easier time customizing and building their own AI enabled applications. + +#### Input audio files + +Whisper.cpp requires as an input 16-bit WAV audio files. +To convert your input audio files to 16-bit WAV format you can use `ffmpeg` like this: + +```bash +ffmpeg -i -ar 16000 -ac 1 -c:a pcm_s16le +``` diff --git a/chatbot/recipes/audio/audio_to_text/ai-lab.yaml b/chatbot/recipes/audio/audio_to_text/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b7fbb37a84f5d9216ff97eb54540feb3dde9d189 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: audio + name: Audio Transcription + description: Transcribe audio files via speech recognition. + containers: + - name: whispercpp-server + contextdir: ../../../model_servers/whispercpp + containerfile: ./base/Containerfile + model-service: true + backend: + - whisper-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/whispercpp:latest + - name: whispercpp-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/redhat-et/locallm-whisper-client:latest diff --git a/chatbot/recipes/audio/audio_to_text/app/Containerfile b/chatbot/recipes/audio/audio_to_text/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..701aead23cff6ed728a39b33e4736f7f908d17d5 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /locallm +COPY requirements.txt /locallm/requirements.txt +RUN pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade -r requirements.txt +COPY whisper_client.py whisper_client.py +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "whisper_client.py" ] diff --git a/chatbot/recipes/audio/audio_to_text/app/requirements.txt b/chatbot/recipes/audio/audio_to_text/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..0c8008dc1904158aea56374bb4a637609b0e993c --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/app/requirements.txt @@ -0,0 +1,3 @@ +streamlit +requests +ffmpeg \ No newline at end of file diff --git a/chatbot/recipes/audio/audio_to_text/app/whisper_client.py b/chatbot/recipes/audio/audio_to_text/app/whisper_client.py new file mode 100644 index 0000000000000000000000000000000000000000..225a38bfdd63cdfd88653dde3c4a24cd2c98b707 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/app/whisper_client.py @@ -0,0 +1,26 @@ +import streamlit as st +import requests +import os +import ffmpeg + +st.set_page_config(page_title="Whisper Speech Recognition", page_icon=":studio_microphone:") +st.title(":studio_microphone: Speech Recognition") +st.markdown("Upload an audio file you wish to have translated") +endpoint = os.getenv("MODEL_ENDPOINT", default="http://0.0.0.0:8001") +endpoint = f"{endpoint}/inference" +endpoint_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +request_kwargs = {} +if endpoint_bearer is not None: + request_kwargs["headers"] = {"Authorization": f"Bearer {endpoint_bearer}"} +audio = st.file_uploader("", type=["wav","mp3","mp4","flac"], accept_multiple_files=False) +# read audio file +if audio: + audio_bytes = audio.read() + st.audio(audio_bytes, format='audio/wav', start_time=0) + request_kwargs["files"] = {'file': audio_bytes} + response = requests.post(endpoint, **request_kwargs) + response_json = response.json() + st.subheader(f"Translated Text") + st.text_area(label="", value=response_json['text'], height=300) +else: + st.write("Input not provided") diff --git a/chatbot/recipes/audio/audio_to_text/bootc/Containerfile b/chatbot/recipes/audio/audio_to_text/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..6a6c0921899bab0f75c6697117c112133dbbf399 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/bootc/Containerfile @@ -0,0 +1,47 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/audio-to-text, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=audio-to-text +ARG MODEL_IMAGE=quay.io/ai-lab/whisper-small:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/whispercpp:latest +ARG TARGETARCH + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf && \ + mkdir -p /usr/lib/containers/storage + +# Prepull the model, model_server & application images to populate the system. +# Use the Containerfile.nocache Containerfile to keep bootc image smaller. +RUN skopeo --debug copy --override-arch=${TARGETARCH} docker://${APP_IMAGE} containers-storage:[overlay@/usr/lib/containers/storage+/run/containers/storage]${APP_IMAGE} +RUN echo ${APP_IMAGE}; skopeo copy --override-arch=${TARGETARCH} docker://${SERVER_IMAGE} containers-storage:[overlay@/usr/lib/containers/storage+/run/containers/storage]${SERVER_IMAGE} +RUN skopeo copy --override-arch=${TARGETARCH} docker://${Model_IMAGE} containers-storage:[overlay@/usr/lib/containers/storage+/run/containers/storage]${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null + +EXPOSE 8501 diff --git a/chatbot/recipes/audio/audio_to_text/bootc/Containerfile.nocache b/chatbot/recipes/audio/audio_to_text/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..568d75219caadcab3c00b44956a37b40d59c0153 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/bootc/Containerfile.nocache @@ -0,0 +1,24 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/audio-to-text, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=audio-to-text + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +EXPOSE 8501 diff --git a/chatbot/recipes/audio/audio_to_text/bootc/README.md b/chatbot/recipes/audio/audio_to_text/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d7512a923265e1e8b31dcaf0d4d11bd95275b26e --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample chat application. + +Details on the application can be found [in the audio-to-text/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/audio-to-text + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/llamacpp_python_cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The audio-to-text can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status audio-to-text +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop audio-to-text +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/audio-to-text.yaml +``` diff --git a/chatbot/recipes/audio/audio_to_text/quadlet/README.md b/chatbot/recipes/audio/audio_to_text/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..442b42dd4c82052bb658fd48996a209ae1949508 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/quadlet/README.md @@ -0,0 +1,29 @@ +### Run audio-to-text as a systemd service + +There are pre-built images and a pod definition to run this audio-to-text example application. +This sample converts an audio waveform (.wav) file to text. + +To run locally, + +```bash +podman kube play ./build/audio-to-text.yaml +``` +To monitor locally, + +```bash +podman pod list +podman ps +podman logs +``` + +The application should be accessible at `http://localhost:8501`. It will take a few minutes for the model to load. + +### Run audio-to-text as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/audio-to-text.yaml ../build/audio-to-text.kube ../build/audio-to-text.image /usr/share/containers/systemd/ +/usr/libexec/podman/quadlet --dryrun (optional) +systemctl daemon-reload +systemctl start audio-to-text +``` diff --git a/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.image b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.image new file mode 100644 index 0000000000000000000000000000000000000000..37862f2e35960605b5ae22cdbfce899bb1b77872 --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.image @@ -0,0 +1,7 @@ +[Install] +WantedBy=audio-to-text.service + +[Image] +Image=APP_IMAGE +Image=MODEL_IMAGE +Image=SERVER_IMAGE diff --git a/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.kube b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.kube new file mode 100644 index 0000000000000000000000000000000000000000..d50d0ffb979280cb0deef6cafb826fd7dd21a79c --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Python script to run against downloaded LLM +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=audio-to-text.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.yaml b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.yaml new file mode 100644 index 0000000000000000000000000000000000000000..168ff283021c2fda3099200df7f7926ca8105b2f --- /dev/null +++ b/chatbot/recipes/audio/audio_to_text/quadlet/audio-to-text.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: audio-to-text + name: audio-to-text +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/ggml-small.bin", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: audio-to-text + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: whisper-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/common/Makefile.common b/chatbot/recipes/common/Makefile.common new file mode 100644 index 0000000000000000000000000000000000000000..26ea7948eed35815b3aa7510802d7548c134920d --- /dev/null +++ b/chatbot/recipes/common/Makefile.common @@ -0,0 +1,234 @@ +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab + +IMAGE_NAME ?= $(REGISTRY_ORG)/${APP}:latest +APP_IMAGE ?= $(REGISTRY)/$(IMAGE_NAME) +CHROMADB_IMAGE ?= $(REGISTRY)/$(REGISTRY_ORG)/chromadb:latest +MODEL_IMAGE ?= $(REGISTRY)/$(REGISTRY_ORG)/granite-7b-lab:latest +SERVER_IMAGE ?= $(REGISTRY)/$(REGISTRY_ORG)/llamacpp_python:latest +SSH_PUBKEY ?= $(shell cat ${HOME}/.ssh/id_rsa.pub;) +BOOTC_IMAGE ?= quay.io/$(REGISTRY_ORG)/${APP}-bootc:latest +BOOTC_IMAGE_BUILDER ?= quay.io/centos-bootc/bootc-image-builder +DISK_TYPE ?= qcow2 +DISK_UID ?= $(shell id -u) +DISK_GID ?= $(shell id -g) +FROM ?= +ARCH ?= +BUILD_ARG_FILE ?= +CONTAINERFILE ?= Containerfile +GRAPH_ROOT=$(shell podman info --format '{{ .Store.GraphRoot }}') +UMASK=$(shell umask) + +ROOTLESS_AUTH_JSON=${XDG_RUNTIME_DIR}/containers/auth.json +ROOTFUL_AUTH_JSON=/run/containers/0/auth.json +NONLINUX_AUTH_JSON=${HOME}/.config/containers/auth.json +AUTH_JSON ?= + +ifneq ("$(wildcard $(NONLINUX_AUTH_JSON))","") + AUTH_JSON=$(NONLINUX_AUTH_JSON) +else ifneq ("$(wildcard $(ROOTLESS_AUTH_JSON))","") + AUTH_JSON=$(ROOTLESS_AUTH_JSON) +else ifneq ("$(wildcard $(ROOTFUL_AUTH_JSON))","") + AUTH_JSON=$(ROOTFUL_AUTH_JSON) +endif + +CHROMEDRIVER_VERSION := 103.0.5060.53 +CHROMEDRIVER_MIRROR := https://chromedriver.storage.googleapis.com +CHROMEDRIVER_DOWNLOAD_PATH := +RECIPE_BINARIES_PATH ?= +CHROME_DOWNLOAD_PATH ?= + +OS := $(shell uname -s) +ARCH := $(shell uname -m) +ifeq ($(ARCH),x86_64) + ARCH := amd64 +endif + +ifeq ($(OS),Darwin) # This structure may vary if we upgrade chromedriver, see index: https://chromedriver.storage.googleapis.com/index.html + ifeq ($(ARCH),amd64) + CHROMEDRIVER_DOWNLOAD_PATH := chromedriver_mac64.zip + else ifeq ($(ARCH),arm64) + CHROMEDRIVER_DOWNLOAD_PATH := chromedriver_mac64_m1.zip + endif + CHROME_DOWNLOAD_PATH := googlechrome.dmg +else ifeq ($(OS),Linux) + CHROMEDRIVER_DOWNLOAD_PATH := chromedriver_linux64.zip + CHROME_DOWNLOAD_PATH := google-chrome-stable_current_$(ARCH).deb +endif + +CHROME_MIRROR := https://www.slimjet.com/chrome/files/$(CHROMEDRIVER_VERSION)/$(CHROME_DOWNLOAD_PATH) + +LOCAL_CHROMEDRIVER_EXISTS ?= $(shell command -v $(RECIPE_BINARIES_PATH)/chromedriver) +UNZIP_EXISTS ?= $(shell command -v unzip) + +RELATIVE_MODELS_PATH := ?= +RELATIVE_TESTS_PATH := ?= + +GRANITE_MODEL_NAME := granite-7b-lab-Q4_K_M.gguf +GRANITE_MODEL_URL := https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf + +MODEL_NAME ?= $(GRANITE_MODEL_NAME) + +.PHONY: install +install:: + $(MAKE) install-chromedriver RECIPE_BINARIES_PATH=${RECIPE_BINARIES_PATH} + $(MAKE) install-chrome RECIPE_BINARIES_PATH=${RECIPE_BINARIES_PATH} + pip install -q -r ${RELATIVE_TESTS_PATH}/requirements.txt + +.PHONY: build +build: + podman build --squash-all \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(FROM:%=--from %) -t ${APP_IMAGE} app/ + +.PHONY: bootc +bootc: quadlet growfs + podman build \ + $(ARCH:%=--arch %) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(FROM:%=--from %) \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + --security-opt label=disable \ + --cap-add SYS_ADMIN \ + --build-arg MODEL_IMAGE=$(MODEL_IMAGE) \ + --build-arg APP_IMAGE=$(APP_IMAGE) \ + --build-arg SERVER_IMAGE=$(SERVER_IMAGE) \ + --build-arg "SSHPUBKEY=$(SSH_PUBKEY)" \ + -f bootc/$(CONTAINERFILE) \ + -t ${BOOTC_IMAGE} bootc + @echo "" + @echo "Successfully built bootc image '${BOOTC_IMAGE}'." + @echo "You may now convert the image into a disk image via bootc-image-builder" + @echo "or the Podman Desktop Bootc Extension. For more information, please refer to" + @echo " * https://github.com/osbuild/bootc-image-builder" + @echo " * https://github.com/containers/podman-desktop-extension-bootc" + +.PHONY: bootc-run +bootc-run: + podman run -d --rm --name $(APP)-bootc -p 8080:8501 --privileged \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + $(BOOTC_IMAGE) /sbin/init + +.PHONY: bootc-image-builder +bootc-image-builder: + @if podman image exists $(BOOTC_IMAGE); then \ + echo "$(BOOTC_IMAGE) exists in local storage, using it"; \ + else \ + $(MAKE) bootc; \ + fi + mkdir -p build/store + podman run \ + --rm \ + -ti \ + -v $(GRAPH_ROOT):/var/lib/containers/storage \ + $(ARCH:%=--arch %) \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + --privileged \ + --pull newer \ + -v ./build:/output \ + -v ./build/store:/store \ + $(BOOTC_IMAGE_BUILDER) \ + $(ARCH:%=--target-arch %) \ + --type $(DISK_TYPE) \ + --chown $(DISK_UID):$(DISK_GID) \ + --local \ + $(BOOTC_IMAGE) + +.PHONY: install-chromedriver +install-chromedriver: + @if [[ -z "$(LOCAL_CHROMEDRIVER_EXISTS)" ]]; then \ + if [[ -n "$(UNZIP_EXISTS)" ]]; then \ + curl --progress-bar --max-time 3000 --connect-timeout 60 -LO $(CHROMEDRIVER_MIRROR)/$(CHROMEDRIVER_VERSION)/$(CHROMEDRIVER_DOWNLOAD_PATH); \ + unzip $(CHROMEDRIVER_DOWNLOAD_PATH); \ + mv chromedriver $(RECIPE_BINARIES_PATH)/; \ + rm ./$(CHROMEDRIVER_DOWNLOAD_PATH); \ + elif [[ -z "$(UNZIP_EXISTS)" ]]; then \ + echo "Install make target requires unizp binary."; \ + exit 1; \ + fi; \ + fi; + +.PHONY: install-chrome +install-chrome: + curl --progress-bar --max-time 3000 --connect-timeout 60 -LO $(CHROME_MIRROR) + @if [[ "$(OS)" == "Linux" ]]; then \ + sudo dpkg -i $(CHROME_DOWNLOAD_PATH); \ + elif [[ "$(OS)" == "Darwin" ]]; then \ + open $(CHROME_DOWNLOAD_PATH); \ + ls "/Volumes/Google Chrome/Google Chrome.app/Contents/MacOS/Google Chrome"; \ + cp -pr "/Volumes/Google Chrome/Google Chrome.app" "$(RECIPE_BINARIES_PATH)/"; \ + diskutil unmount "/Volumes/Google Chrome" || true; \ + rm $(CHROME_DOWNLOAD_PATH); \ + fi; + +.PHONY: check-umask +check-umask: + @test "$(UMASK)" = "0022" || \ + (echo; echo -n "Error: umask $(UMASK) will cause unexpected behaviour: use umask 022! "; \ + echo "Verify the `ai-lab-recipes` git repository was cloned with umask 0022"; exit 1) + +.PHONY: growfs +growfs: quadlet check-umask + # Add growfs service + mkdir -p build; cp -pR ../../common/usr build/ + +.PHONY: quadlet +quadlet: + # Modify quadlet files to match the server, model and app image + rm -rf build; mkdir -p bootc/build; ln -sf bootc/build . + sed -e "s|SERVER_IMAGE|${SERVER_IMAGE}|" \ + -e "s|APP_IMAGE|${APP_IMAGE}|g" \ + -e "s|MODEL_IMAGE|${MODEL_IMAGE}|g" \ + -e "s|APP|${APP}|g" \ + quadlet/${APP}.image \ + > build/${APP}.image + sed -e "s|SERVER_IMAGE|${SERVER_IMAGE}|" \ + -e "s|APP_IMAGE|${APP_IMAGE}|g" \ + -e "s|MODEL_IMAGE|${MODEL_IMAGE}|g" \ + quadlet/${APP}.yaml \ + > build/${APP}.yaml + cp -p quadlet/${APP}.kube build/${APP}.kube + +.PHONY: run +run: + podman run -it -p $(PORT):$(PORT) -e MODEL_ENDPOINT=http://10.88.0.1:8001 ${APP_IMAGE} + +.PHONY: clean +clean: + -rm -rf build bootc/build + -rm -rf tests/__pycache__ + -rm -f ./$(MODEL_NAME) &> /dev/null + +.PHONY: check-model-in-path +check-model-in-path: + @if [ ! -f "../../../models/$(MODEL_NAME)" ]; then \ + echo "Model file -- $(MODEL_NAME) -- not present in the models directory."; \ + exit 1; \ + else \ + if [ ! -f "./$(MODEL_NAME)" ]; then \ + ln -s ../../../models/$(MODEL_NAME) ./$(MODEL_NAME); \ + fi; \ + fi; + +.PHONY: functional-tests +functional-tests: + $(MAKE) MODEL_NAME=$(MODEL_NAME) check-model-in-path + @if [[ -n "$(LOCAL_CHROMEDRIVER_EXISTS)" ]]; then \ + IMAGE_NAME=${IMAGE_NAME} REGISTRY=${REGISTRY} MODEL_NAME=${MODEL_NAME} pytest -vvv --driver=Chrome --driver-path=$(RECIPE_BINARIES_PATH)/chromedriver ${RELATIVE_TESTS_PATH}/functional; \ + else \ + echo "fetching chromedriver"; \ + make install; \ + IMAGE_NAME=${IMAGE_NAME} REGISTRY=${REGISTRY} MODEL_NAME=${MODEL_NAME} pytest -vvv --driver=Chrome --driver-path=$(RECIPE_BINARIES_PATH)/chromedriver ${RELATIVE_TESTS_PATH}/functional; \ + fi; + +.PHONY: integration-tests +integration-tests: + @if [[ -n "$(LOCAL_CHROMEDRIVER_EXISTS)" ]]; then \ + URL=${URL} IMAGE_NAME=${IMAGE_NAME} REGISTRY=${REGISTRY} pytest -vvv --driver=Chrome --driver-path=$(RECIPE_BINARIES_PATH)/chromedriver ${RELATIVE_TESTS_PATH}/integration; \ + else \ + echo "fetching chromedriver"; \ + make install; \ + URL=${URL} IMAGE_NAME=${IMAGE_NAME} REGISTRY=${REGISTRY} pytest -vvv --driver=Chrome --driver-path=$(RECIPE_BINARIES_PATH)/chromedriver ${RELATIVE_TESTS_PATH}/integration; \ + fi; + diff --git a/chatbot/recipes/common/README.md b/chatbot/recipes/common/README.md new file mode 100644 index 0000000000000000000000000000000000000000..343d036d178c7e20e46bcc939d9562b1ce3929e2 --- /dev/null +++ b/chatbot/recipes/common/README.md @@ -0,0 +1,42 @@ +# Makefile targets + +| Target | Description | +|-----------------------|-------------------------------------------------------------------------------| +| bootc | Create bootable container image for the application | +| bootc-image-builder | Create diskimage from your bootc image to be run on a VM or physical hardware | +| bootc-run | Run bootable container image as a container | +| build | Build container image to run your app using Containerfile in app directory | +| clean | Remove contents of the build directory | +| install-chrome | Used for testing purposes | +| install-chromedriver | Used to testing purposes | +| quadlet | Modify quadlet files into the build dir | +| run | Run containerizied app as a container | + + +# Makefile variables + +Makefile variables defined within each `recipe` Makefile which can be +used to override defaults for a variety of make targets. + +| Variable | Description | Default | +|--------------------|------------------------------------------------------|---------------------------------------------------------| +|REGISTRY | Container Registry for storing container images | `quay.io` | +|REGISTRY_ORG | Containwer Registry organization | `ai-lab` | +|IMAGE_NAME | App image and registry organization | `$(REGISTRY_ORG)/${APP}:latest` | +|APP_IMAGE | Application image to be built | `$(REGISTRY)/$(IMAGE_NAME)` | +|BOOTC_IMAGE | Bootc image name | `quay.io/$(REGISTRY_ORG)/${APP}-bootc:latest` | +|BOOTC_IMAGE_BUILDER | Bootc Image Builder container image | `quay.io/centos-bootc/bootc-image-builder` | +|CHROMADB_IMAGE | ChromaDB image to be used for application | `$(REGISTRY)/$(REGISTRY_ORG)/chromadb:latest` | +|DISK_TYPE | Disk type to be created by BOOTC_IMAGE_BUILDER | `qcow2` (Options: ami, iso, vmdk, raw) | +|DISK_UID | Disk UID to be specified by BOOTC_IMAGE_BUILDER | `$(shell id -u)` | +|DISK_GID | Disk GID to be specified by BOOTC_IMAGE_BUILDER | `$(shell id -g)` | +|MODEL_IMAGE | AI Model to be used by application | `$(REGISTRY)/$(REGISTRY_ORG)/granite-7b-lab:latest`| +|SERVER_IMAGE | AI Model Server Application | `$(REGISTRY)/$(REGISTRY_ORG)/llamacpp_python:latest` | +|SSH_PUBKEY | SSH Public key preloaded in bootc image. | `$(shell cat ${HOME}/.ssh/id_rsa.pub;)` | +|FROM | Overrides first FROM instruction within Containerfile| `FROM` line defined in the Containerfile | +|ARCH | Use alternate arch for image build | Current Arch | +|CONTAINERFILE | Use alternate Containfile for image build | Containerfile (Containerfile.nocache) | + +Examples + +make bootc FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 APP_IMAGE=quay.io/myorg/chatbot-bootc diff --git a/chatbot/recipes/common/README_bootc_image_builder.md b/chatbot/recipes/common/README_bootc_image_builder.md new file mode 100644 index 0000000000000000000000000000000000000000..a94183e4e0a5b83a524cdc35d6cb787464e2909d --- /dev/null +++ b/chatbot/recipes/common/README_bootc_image_builder.md @@ -0,0 +1,16 @@ +This tools allows you to build and deploy disk-images from bootc container inputs. + +The following image disk types are currently available: + +| Image type | Target environment | +|-----------------------|---------------------------------------------------------------------------------------| +| `ami` | [Amazon Machine Image](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) | +| `qcow2` **(default)** | [QEMU](https://www.qemu.org/) | +| `vmdk` | [VMDK](https://en.wikipedia.org/wiki/VMDK) usable in vSphere, among others | +| `anaconda-iso` | An unattended Anaconda installer that installs to the first disk found. | +| `raw` | Unformatted [raw disk](https://en.wikipedia.org/wiki/Rawdisk). | + +The recipe Makefile can be used to automatically generate a disk image from the bootc image. The following +command will create an qcow2 image file from the default bootc image in the build subdirectory. + +`make bootc-image-builder DISK_TYPE=qcow2` diff --git a/chatbot/recipes/common/bin/.gitkeep b/chatbot/recipes/common/bin/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/recipes/common/quadlet/app.image b/chatbot/recipes/common/quadlet/app.image new file mode 100644 index 0000000000000000000000000000000000000000..2eddbfc928dd07a396e9a5c95c680eb20c039ac8 --- /dev/null +++ b/chatbot/recipes/common/quadlet/app.image @@ -0,0 +1,10 @@ +[Install] +WantedBy=APP.service + +[Service] +TimeoutStartSec=infinity + +[Image] +Image=APP_IMAGE +Image=MODEL_IMAGE +Image=SERVER_IMAGE diff --git a/chatbot/recipes/common/usr/lib/systemd/system/bootc-generic-growpart.service b/chatbot/recipes/common/usr/lib/systemd/system/bootc-generic-growpart.service new file mode 100644 index 0000000000000000000000000000000000000000..77bb310b5f07dfd9d0f50147921f59a713e7a6f1 --- /dev/null +++ b/chatbot/recipes/common/usr/lib/systemd/system/bootc-generic-growpart.service @@ -0,0 +1,20 @@ +[Unit] +Description=Bootc Fallback Root Filesystem Grow +Documentation=https://gitlab.com/fedora/bootc/docs +# For now we skip bare metal cases, and we also have nothing to do +# for containers. +ConditionVirtualization=vm +# This helps verify that we're running in a bootc/ostree based target. +ConditionPathIsMountPoint=/sysroot +# We want to run before any e.g. large container images might be pulled. +DefaultDependencies=no +Requires=sysinit.target +After=sysinit.target +Before=basic.target + +[Service] +ExecStart=/usr/libexec/bootc-generic-growpart +# So we can temporarily remount the sysroot writable +MountFlags=slave +# Just to auto-cleanup our temporary files +PrivateTmp=yes diff --git a/chatbot/recipes/common/usr/libexec/bootc-generic-growpart b/chatbot/recipes/common/usr/libexec/bootc-generic-growpart new file mode 100644 index 0000000000000000000000000000000000000000..c2277ba33668ebb5735002221543fee119abe7d8 --- /dev/null +++ b/chatbot/recipes/common/usr/libexec/bootc-generic-growpart @@ -0,0 +1,41 @@ +#!/bin/bash +set -eu + +backing_device=$(findmnt -vno SOURCE /sysroot) +echo "Backing device: ${backing_device}" +syspath=/sys/class/block/$(basename "${backing_device}") +if ! test -d "${syspath}"; then + echo "failed to find backing device ${syspath}"; exit 1 +fi + +# Handling devicemapper targets is a whole other thing +case $backing_device in + /dev/mapper/*) "Not growing $backing_device"; exit 0 ;; +esac + +# Note that we expect that the rootfs is on a partition +partition=$(cat "${syspath}"/partition) + +# Walk up to find the parent blockdev +parentpath=$(dirname "$(realpath "${syspath}")") +devmajmin=$(cat "${parentpath}"/dev) +parent="/dev/block/${devmajmin}" + +# Grow the partition +tmpf=$(mktemp) +# Ignore errors because growpart exits 1 if nothing changed; +# we need to check the output for NOCHANGE: +if ! /usr/bin/growpart "${parent}" "${partition}" > "${tmpf}"; then + cat "${tmpf}" + if grep -qEe '^NOCHANGE: ' "${tmpf}"; then + exit 0 + fi + echo "growpart failed" + exit 1 +fi +cat "${tmpf}" +# Now, temporarily remount the sysroot writable in our mount namespace +mount -o remount,rw /sysroot +# And defer to systemd's growfs wrapper which handles dispatching on +# the target filesystem type. +/usr/lib/systemd/systemd-growfs /sysroot diff --git a/chatbot/recipes/computer_vision/object_detection/Makefile b/chatbot/recipes/computer_vision/object_detection/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b18a45b363f046edb03f5a7939263ca68093a61f --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/Makefile @@ -0,0 +1,15 @@ +SHELL := /bin/bash +APP ?= object_detection_client +PORT ?= 8501 +MODEL_NAME ?= facebook/detr-resnet-101 + +include ../../common/Makefile.common + +.PHONY: functional-tests +functional-tests: + IMAGE_NAME=${IMAGE_NAME} REGISTRY=${REGISTRY} MODEL_NAME=${MODEL_NAME} pytest -vvv --driver=Chrome --driver-path=$(RECIPE_BINARIES_PATH)/chromedriver ${RELATIVE_TESTS_PATH}/functional + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests + diff --git a/chatbot/recipes/computer_vision/object_detection/README.md b/chatbot/recipes/computer_vision/object_detection/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cdb55af11b15464ec9832550aff3c18eb7bd554a --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/README.md @@ -0,0 +1,92 @@ +# Object Detection + +This recipe helps developers start building their own custom AI enabled object detection applications. It consists of two main components: the Model Service and the AI Application. + +There are a few options today for local Model Serving, but this recipe will use our FastAPI [`object_detection_python`](../../../model_servers/object_detection_python/src/object_detection_server.py) model server. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/object_detection_python/base/Containerfile`](/model_servers/object_detection_python/base/Containerfile). + +The AI Application will connect to the Model Service via an API. The recipe relies on [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the object detection application below. + +![](/assets/object_detection.png) + +## Try the Object Detection Application: + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Object Detection` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will go into greater detail on how each container in the application above is built, run, and what purpose it serves in the overall application. All the Model Server elements of the recipe use a central Model Server [Makefile](../../../model_servers/common/Makefile.common) that includes variables populated with default values to simplify getting started. Currently we do not have a Makefile for the Application elements of the Recipe, but this coming soon, and will leverage the recipes common [Makefile](../../common/Makefile.common) to provide variable configuration and reasonable defaults to this Recipe's application. + +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) + +## Download a model + +If you are just getting started, we recommend using [facebook/detr-resnet-101](https://huggingface.co/facebook/detr-resnet-101). +This is a well performant model with an Apache-2.0 license. +It's simple to download a copy of the model from [huggingface.co](https://huggingface.co) + +You can use the `download-model-facebook-detr-resnet-101` make target in the `model_servers/object_detection_python` directory to download and move the model into the models directory for you: + +```bash +# from path model_servers/object_detection_python from repo containers/ai-lab-recipes + make download-model-facebook-detr-resnet-101 +``` + +## Build the Model Service + +The You can build the Model Service from the [object_detection_python model-service directory](../../../model_servers/object_detection_python). + +```bash +# from path model_servers/object_detection_python from repo containers/ai-lab-recipes +make build +``` + +Checkout the [Makefile](../../../model_servers/object_detection_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from the [`model_servers/object_detection_python`](../../../model_servers/object_detection_python) directory, which will be set with reasonable defaults: + +```bash +# from path model_servers/object_detection_python from repo containers/ai-lab-recipes +make run +``` + +As stated above, by default the model service will use [`facebook/detr-resnet-101`](https://huggingface.co/facebook/detr-resnet-101). However you can use other compatible models. Simply pass the new `MODEL_NAME` and `MODEL_PATH` to the make command. Make sure the model is downloaded and exists in the [models directory](../../../models/): + +```bash +# from path model_servers/object_detection_python from repo containers/ai-lab-recipes +make MODEL_NAME=facebook/detr-resnet-50 MODEL_PATH=/models/facebook/detr-resnet-101 run +``` + +## Build the AI Application + +Now that the Model Service is running we want to build and deploy our AI Application. Use the provided Containerfile to build the AI Application +image from the [`object_detection/`](./) recipe directory. + +```bash +# from path recipes/computer_vision/object_detection from repo containers/ai-lab-recipes +podman build -t object_detection_client . +``` + +### Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. +When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. +This could be any appropriately hosted Model Service (running locally or in the cloud) using a compatible API. +The following Podman command can be used to run your AI Application: + +```bash +podman run -p 8501:8501 -e MODEL_ENDPOINT=http://10.88.0.1:8000 object_detection_client +``` + +### Interact with the AI Application + +Once the client is up a running, you should be able to access it at `http://localhost:8501`. From here you can upload images from your local machine and detect objects in the image as shown below. + +By using this recipe and getting this starting point established, +users should now have an easier time customizing and building their own AI enabled applications. diff --git a/chatbot/recipes/computer_vision/object_detection/ai-lab.yaml b/chatbot/recipes/computer_vision/object_detection/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f9adcf47761409832f2d5b19db07589ff4ddae17 --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: vision + name: Object Detection + description: Detect and classify objects in images. + containers: + - name: object-detection-server + contextdir: ../../../model_servers/object_detection_python + containerfile: ./base/Containerfile + model-service: true + backend: + - pytorch + arch: + - arm64 + - amd64 + ports: + - 8000 + image: quay.io/redhat-et/locallm-object-detection-server:latest + - name: object-detection-client + contextdir: ./app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/redhat-et/locallm-object-detection-client:latest diff --git a/chatbot/recipes/computer_vision/object_detection/app/Containerfile b/chatbot/recipes/computer_vision/object_detection/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..7b42097b0cd63710939a5b1a6864b8e2cd0af45d --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /locallm +COPY requirements.txt /locallm/requirements.txt +RUN pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade -r requirements.txt +COPY object_detection_client.py object_detection_client.py +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "object_detection_client.py" ] diff --git a/chatbot/recipes/computer_vision/object_detection/app/object_detection_client.py b/chatbot/recipes/computer_vision/object_detection/app/object_detection_client.py new file mode 100644 index 0000000000000000000000000000000000000000..7f61451bfb7944c195b3e68ac5410f866762d59a --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/app/object_detection_client.py @@ -0,0 +1,41 @@ +import streamlit as st +import base64 +import requests +from PIL import Image +import os +import io + +st.title("🕵️‍♀️ Object Detection") +endpoint =os.getenv("MODEL_ENDPOINT", default = "http://0.0.0.0:8000") +endpoint_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +headers = {"accept": "application/json", + "Content-Type": "application/json"} +if endpoint_bearer: + headers["Authorization"] = f"Bearer {endpoint_bearer}" +image = st.file_uploader("Upload Image") +window = st.empty() + +if image: + #Ensure image dimensions are appropriate + img = Image.open(io.BytesIO(image.read())) + scale_factor = (500 * 500)/(img.height * img.width) + if scale_factor < 0.20: + scale_factor = 0.20 + img = img.resize((int(img.width * scale_factor) , + int(img.height * scale_factor))) + window.image(img, use_column_width=True) + # convert PIL image into bytes for post request + bytes_io = io.BytesIO() + if img.mode in ("RGBA", "P"): + img = img.convert("RGB") + img.save(bytes_io, "JPEG") + img_bytes = bytes_io.getvalue() + b64_image = base64.b64encode(img_bytes).decode('utf-8') + data = {'image': b64_image} + response = requests.post(f'{endpoint}/detection', headers=headers,json=data, verify=False) + # parse response and display outputs + response_json = response.json() + image = response_json["image"] + window.image(base64.b64decode(image), use_column_width=True) + for box in response_json["boxes"]: + st.markdown(box) diff --git a/chatbot/recipes/computer_vision/object_detection/app/requirements.txt b/chatbot/recipes/computer_vision/object_detection/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..47d2ad559ad5280b3aaf22f5290382d11d1a94af --- /dev/null +++ b/chatbot/recipes/computer_vision/object_detection/app/requirements.txt @@ -0,0 +1,40 @@ +altair==5.3.0 +attrs==23.2.0 +blinker==1.7.0 +cachetools==5.3.3 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +gitdb==4.0.11 +GitPython==3.1.43 +idna==3.7 +Jinja2==3.1.4 +jsonschema==4.21.1 +jsonschema-specifications==2023.12.1 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +mdurl==0.1.2 +numpy==1.26.4 +packaging==24.0 +pandas==2.2.3 +pillow==10.3.0 +protobuf==4.25.3 +pyarrow==15.0.2 +pydeck==0.8.1b0 +Pygments==2.17.2 +python-dateutil==2.9.0.post0 +pytz==2024.1 +referencing==0.34.0 +requests==2.31.0 +rich==13.7.1 +rpds-py==0.18.1 +six==1.16.0 +smmap==5.0.1 +streamlit==1.33.0 +tenacity==8.2.3 +toml==0.10.2 +toolz==0.12.1 +tornado==6.4.1 +typing_extensions==4.11.0 +tzdata==2024.1 +urllib3==2.2.3 diff --git a/chatbot/recipes/computer_vision/tests/conftest.py b/chatbot/recipes/computer_vision/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..7a2206fabb716996061f6288dfa3db482e92b2a1 --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest +import os + + +@pytest.fixture +def chrome_options(chrome_options): + chrome_options.add_argument("--headless") + return chrome_options diff --git a/chatbot/recipes/computer_vision/tests/functional/__init__.py b/chatbot/recipes/computer_vision/tests/functional/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/recipes/computer_vision/tests/functional/conftest.py b/chatbot/recipes/computer_vision/tests/functional/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..35f5137b382b177678b67bfea19cdea4fd671217 --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/functional/conftest.py @@ -0,0 +1,58 @@ +import pytest_container +import os +import logging + +REGISTRY=os.environ['REGISTRY'] +IMAGE_NAME=os.environ['IMAGE_NAME'] +MODEL_NAME=os.environ['MODEL_NAME'] + +logging.info(""" +Starting pytest with the following ENV vars: + REGISTRY: {REGISTRY} + IMAGE_NAME: {IMAGE_NAME} + MODEL_NAME: {MODEL_NAME} +For: + model_server: whispercpp +""".format(REGISTRY=REGISTRY, IMAGE_NAME=IMAGE_NAME, MODEL_NAME=MODEL_NAME)) + + +MS = pytest_container.Container( + url=f"containers-storage:{REGISTRY}/{IMAGE_NAME}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"/locallm/models/${MODEL_NAME}", + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": f"/locall/models/{MODEL_NAME}", + "HOST": "0.0.0.0", + "PORT": "8001" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=8001, + host_port=8001 + ) + ], + ) + +CB = pytest_container.Container( + url=f"containers-storage:{os.environ['REGISTRY']}/containers/{os.environ['IMAGE_NAME']}", + extra_environment_variables={ + "MODEL_ENDPOINT": "http://10.88.0.1:8001" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=8501, + host_port=8501 + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/chatbot/recipes/computer_vision/tests/functional/test_app.py b/chatbot/recipes/computer_vision/tests/functional/test_app.py new file mode 100644 index 0000000000000000000000000000000000000000..73cf26de7c733189e63e71f4c4d7cdc345fbc44a --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/functional/test_app.py @@ -0,0 +1,17 @@ +import pytest_container +from .conftest import CB +import tenacity + +CONTAINER_IMAGES = [CB] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip() + +def test_title(auto_container: pytest_container.container.ContainerData, selenium): + selenium.get(f"http://localhost:{auto_container.forwarded_ports[0].host_port}") + assert selenium.title == "Streamlit" + diff --git a/chatbot/recipes/computer_vision/tests/integration/__init__.py b/chatbot/recipes/computer_vision/tests/integration/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/recipes/computer_vision/tests/integration/conftest.py b/chatbot/recipes/computer_vision/tests/integration/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..3a67a71cb6bce16d39980092ed5a325ccac02c99 --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/integration/conftest.py @@ -0,0 +1,7 @@ +import os +import pytest + + +@pytest.fixture() +def url(): + return os.environ["URL"] diff --git a/chatbot/recipes/computer_vision/tests/integration/test_app.py b/chatbot/recipes/computer_vision/tests/integration/test_app.py new file mode 100644 index 0000000000000000000000000000000000000000..e2825e60cc0b1834d2ac9a5881468be5495b9d25 --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/integration/test_app.py @@ -0,0 +1,3 @@ +def test_title(url,selenium): + selenium.get(f"http://{url}:8501") + assert selenium.title == "Streamlit" diff --git a/chatbot/recipes/computer_vision/tests/requirements.txt b/chatbot/recipes/computer_vision/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..22fc97f27976ea5aebcc75d9586703a5b78df887 --- /dev/null +++ b/chatbot/recipes/computer_vision/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/chatbot/recipes/multimodal/image_understanding/README.md b/chatbot/recipes/multimodal/image_understanding/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1c9a5e6fd83e2d77f53018d7a87826bb759d301f --- /dev/null +++ b/chatbot/recipes/multimodal/image_understanding/README.md @@ -0,0 +1,39 @@ +# Image Analysis + +### Build image +```bash +cd image_understanding +podman build -t image_understanding app +``` + +### Run the Multimodal Model Server: + +#### 1. Get a Multimodal Model: + +For this example we will use the pre-quantized gguf version of LLava-v1.5. You can find the model files here: + +https://huggingface.co/mys/ggml_llava-v1.5-7b/tree/main + +The multimodal models require two gguf files. Please download the following two files to `models/`. + +* [ggml-model-q4_k.gguf](https://huggingface.co/mys/ggml_llava-v1.5-7b/tree/main) +* [mmproj-model-f16.gguf](https://huggingface.co/mys/ggml_llava-v1.5-7b/tree/main) + + + +#### 2. Run Multi-Modal Model Server: +Once you have the model files you can run the model server image locally. +```bash +podman run -it -p 8001:8001 -v /locallm/models:/locallm/models:Z -e MODEL_PATH=models/ggml-model-q4_k.gguf -e CLIP_MODEL_PATH=models/mmproj-model-f16.gguf -e CHAT_FORMAT=llava-1-5 -e HOST=0.0.0.0 -e PORT=8001 playground +``` + +### Run AI Application Image Locally + +```bash +podman run --rm -it -p 8501:8501 -e MODEL_ENDPOINT=http://10.88.0.1:8001 image_understanding +``` + +Interact with the application from your local browser at `localhost:8501`. You can upload an image file from your host machine and the app will provide a natural language description of the image. + + +![](/assets/image_analysis.png) diff --git a/chatbot/recipes/multimodal/image_understanding/ai-lab.yaml b/chatbot/recipes/multimodal/image_understanding/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e7d09e193dd1aea6a60fde225197aae99f8c4fcb --- /dev/null +++ b/chatbot/recipes/multimodal/image_understanding/ai-lab.yaml @@ -0,0 +1,20 @@ +application: + type: Multimodal + name: image-understanding + description: Analyze and describe the content of images. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + - name: image-understanding-inference-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 diff --git a/chatbot/recipes/multimodal/image_understanding/app/Containerfile b/chatbot/recipes/multimodal/image_understanding/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..de579da9a1e3581822bbdc91ee6cf3382bb31671 --- /dev/null +++ b/chatbot/recipes/multimodal/image_understanding/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /image_understanding +COPY requirements.txt . +RUN pip install --upgrade pip && \ + pip install --no-cache-dir --upgrade -r requirements.txt +COPY image_understanding.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "image_understanding.py" ] diff --git a/chatbot/recipes/multimodal/image_understanding/app/image_understanding.py b/chatbot/recipes/multimodal/image_understanding/app/image_understanding.py new file mode 100644 index 0000000000000000000000000000000000000000..20dda679904cb6afd70eeabcfd515956a7648b6d --- /dev/null +++ b/chatbot/recipes/multimodal/image_understanding/app/image_understanding.py @@ -0,0 +1,38 @@ +from openai import OpenAI +import streamlit as st +import base64 +import os + +model_service = os.getenv("MODEL_ENDPOINT", + default="http://localhost:8001") + +st.title("📷 Image Analysis") +image = st.file_uploader("Upload Image:",) +top_container = st.container(border=True) +if image is not None: + b64_image = base64.b64encode(image.read()).decode("utf-8") + client = OpenAI(base_url=f'{model_service}/v1', + api_key="sk-xxx") + with st.spinner("Analyzing Image..."): + st.image(image) + response = client.chat.completions.create( + model="", + stream=True, + messages=[ + { + "role": "user", + "content": [ + { + "type": "image_url", + "image_url": { + "url": f"data:image/png;base64,{b64_image}" + }, + }, + {"type": "text", + "text": "What does the image say"}, + ], + } + ], + ) + top_container.chat_message("assistant").write_stream(response) + \ No newline at end of file diff --git a/chatbot/recipes/multimodal/image_understanding/app/requirements.txt b/chatbot/recipes/multimodal/image_understanding/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..7f355e25bb634fd78c05d71c6d6d4ea06569c36e --- /dev/null +++ b/chatbot/recipes/multimodal/image_understanding/app/requirements.txt @@ -0,0 +1,2 @@ +openai +streamlit \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/agents/Makefile b/chatbot/recipes/natural_language_processing/agents/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..673576d7449798f1d0d4749f229dab74d38a1385 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= react_agent +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/natural_language_processing/agents/README.md b/chatbot/recipes/natural_language_processing/agents/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b843483ae6aa7c9ac144c234dda468497bf731fb --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/README.md @@ -0,0 +1,182 @@ +# ReAct Agent Application + +This recipe demonstrates the ReAct (Reasoning and Acting) framework in action through a music exploration application. ReAct enables AI to think step-by-step about tasks, take appropriate actions, and provide reasoned responses. The application shows how ReAct can be used to create an intelligent music discovery assistant that combines reasoning with Spotify API interactions. + +The application utilizes [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) for the Model Service and integrates with Spotify's API for music data. The recipe uses [Langchain](https://python.langchain.com/docs/get_started/introduction) for the ReAct implementation and [Streamlit](https://streamlit.io/) for the UI layer. + +## Spotify API Access +To use this application, you'll need Spotify API credentials: +- Create a Spotify Developer account +- Create an application in the Spotify Developer Dashboard +- Get your Client ID and Client Secret + +These can be provided through environment variables or the application's UI. + +## Try the ReAct Agent Application +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `ReAct Agent` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/chatbot.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/chatbot.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `chatbot`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead. +Please refer to the section below for more details about [interacting with the chatbot application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop chatbot +podman pod rm chatbot +``` + +## Download a model + +If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well +performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/chatbot +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the chat application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled chatbot applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample chatbot workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the chatbot application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/chatbot-bootc:latest +``` + +Upon a reboot, you'll see that the chatbot service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status chatbot +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/agents/ai-lab.yaml b/chatbot/recipes/natural_language_processing/agents/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..5898cdad639274619141c6d57d26b133f3706e60 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: ReAct_Agent_Streamlit + description: ReAct framework implementation with Spotify API integration in a web frontend + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: streamlit-react-agent-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/react-agent:latest diff --git a/chatbot/recipes/natural_language_processing/agents/app/Containerfile b/chatbot/recipes/natural_language_processing/agents/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..350139c57b001fe627e8be11ad4ff2235b06a7f6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /agents +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /function-call/requirements.txt +COPY *.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "app.py" ] diff --git a/chatbot/recipes/natural_language_processing/agents/app/react-agent-app.py b/chatbot/recipes/natural_language_processing/agents/app/react-agent-app.py new file mode 100644 index 0000000000000000000000000000000000000000..c06d203450187ba227573c0420a8f7c30309edfe --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/app/react-agent-app.py @@ -0,0 +1,386 @@ +import os +from typing import Dict, List +import requests +import time +import json +import streamlit as st +from langchain_core.tools import BaseTool +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.messages import AIMessage, HumanMessage +from dotenv import load_dotenv +from typing import Any, List, Dict, Union + +# Load env file +load_dotenv() + +# Model service +model_service = os.getenv("MODEL_ENDPOINT", "http://localhost:8001") +model_service = f"{model_service}/v1" + +# Spotify API Configuration +SPOTIFY_BASE_URL = "https://api.spotify.com/v1" + +class SpotifyAPI: + def __init__(self): + self.client_id = os.getenv("SPOTIFY_CLIENT_ID") + self.client_secret = os.getenv("SPOTIFY_CLIENT_SECRET") + + # If not in .env, access it through UI + if not self.client_id or not self.client_secret: + if hasattr(st.session_state, 'spotify_client_id') and hasattr(st.session_state, 'spotify_client_secret'): + self.client_id = st.session_state.spotify_client_id + self.client_secret = st.session_state.spotify_client_secret + + if not self.client_id or not self.client_secret: + raise ValueError("Spotify credentials not found. Please provide them in the sidebar.") + + self.access_token = self._get_access_token() + + def _get_access_token(self): + """Get Spotify access token using client credentials flow""" + auth_url = "https://accounts.spotify.com/api/token" + auth_response = requests.post( + auth_url, + data={ + "grant_type": "client_credentials", + "client_id": self.client_id, + "client_secret": self.client_secret, + } + ) + + if auth_response.status_code != 200: + raise Exception("Failed to get access token") + + return auth_response.json()["access_token"] + + def search_playlists(self, query: str, limit: int = 5) -> Dict: + """Search for playlists using Spotify API""" + enhanced_query = f"{query} playlist top popular" + headers = { + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/json" + } + params = { + "q": enhanced_query, + "type": "playlist", + "limit": limit, + "market": "US" + } + + response = requests.get( + f"{SPOTIFY_BASE_URL}/search", + headers=headers, + params=params + ) + + if response.status_code != 200: + raise Exception(f"Search failed: {response.json().get('error', {}).get('message')}") + + return response.json() + + def get_trending_tracks(self, location: str = None, limit: int = 10) -> Dict: + """Get trending tracks for a specific location""" + headers = { + "Authorization": f"Bearer {self.access_token}", + "Content-Type": "application/json" + } + #include location in query + query = f"top charts popular {location}" if location else "top charts" + + params = { + "q": query, + "type": "track", + "limit": limit, + "market": "US", + "offset": 0, + "include_external": "audio" + } + + response = requests.get( + f"{SPOTIFY_BASE_URL}/search", + headers=headers, + params=params + ) + + if response.status_code != 200: + raise Exception(f"Search failed: {response.json().get('error', {}).get('message')}") + + return response.json() + +class SpotifySearchTool(BaseTool): + name: str = "spotify_search" + description: str = """ + Search for playlists on Spotify. + Input should be a search query string. + The tool will return relevant playlists with their details. + """ + spotify: Any = None + + def __init__(self) -> None: + super().__init__() + self.spotify = SpotifyAPI() + + def _run(self, query: str) -> List[Dict]: + try: + results = self.spotify.search_playlists(query) + playlists = [] + for item in results['playlists']['items']: + playlist = { + 'name': item['name'], + 'description': item['description'], + 'tracks_total': item['tracks']['total'], + 'url': item['external_urls']['spotify'], + 'owner': item['owner']['display_name'], + 'followers': item['followers']['total'] if 'followers' in item else 0 + } + playlists.append(playlist) + return playlists + except Exception as e: + return f"Error searching Spotify: {str(e)}" + +class SpotifyTrendingTool(BaseTool): + name: str = "spotify_trending" + description: str = """ + Get trending tracks for a specific location on Spotify. + Input should be a location string (e.g., 'Berkeley', 'Bay Area'). + Returns top trending tracks in that area. + """ + spotify: Any = None + + def __init__(self) -> None: + super().__init__() + self.spotify = SpotifyAPI() + + def _run(self, location: str) -> List[Dict]: + try: + results = self.spotify.get_trending_tracks(location) + tracks = [] + for item in results['tracks']['items']: + track = { + 'name': item['name'], + 'artist': ', '.join([artist['name'] for artist in item['artists']]), + 'album': item['album']['name'], + 'url': item['external_urls']['spotify'], + 'popularity': item['popularity'] + } + tracks.append(track) + return tracks + except Exception as e: + return f"Error getting trending tracks: {str(e)}" + +def format_spotify_response(tool_responses: Dict) -> str: + """Format the Spotify API responses into a readable message""" + response = "" + + # Format trending tracks + trending_tracks = tool_responses.get("trending", []) + if isinstance(trending_tracks, list) and trending_tracks: + response += "📊 Trending Tracks:\n" + for i, track in enumerate(trending_tracks[:5], 1): + response += f"{i}. {track['name']} by {track['artist']}\n" + response += f" - Album: {track['album']}\n" + response += f" - Listen: {track['url']}\n\n" + else: + response += "📊 No trending tracks found for this location.\n\n" + + # Format playlists + playlists = tool_responses.get("playlists", []) + if isinstance(playlists, list) and playlists: + response += "🎵 Related Playlists:\n" + for i, playlist in enumerate(playlists[:3], 1): + response += f"{i}. {playlist['name']}\n" + response += f" - Tracks: {playlist['tracks_total']}\n" + response += f" - Description: {playlist['description']}\n" + response += f" - Listen: {playlist['url']}\n\n" + else: + response += "No related playlists found.\n" + + return response + +# Model service check +@st.cache_resource(show_spinner=False) +def checking_model_service(): + start = time.time() + print("Checking Model Service Availability...") + ready = False + while not ready: + try: + request_cpp = requests.get(f'{model_service}/models') + request_ollama = requests.get(f'{model_service[:-2]}api/tags') + if request_cpp.status_code == 200: + server = "Llamacpp_Python" + ready = True + elif request_ollama.status_code == 200: + server = "Ollama" + ready = True + except: + pass + time.sleep(1) + print(f"{server} Model Service Available") + print(f"Time taken: {time.time()-start} seconds") + return server + +def get_models(): + try: + response = requests.get(f"{model_service[:-2]}api/tags") + return [i["name"].split(":")[0] for i in json.loads(response.content)["models"]] + except: + return None + +# ReAct prompt template +REACT_PROMPT = """You are a helpful assistant that can search for music on Spotify. +You have access to the following tools: + +{tools} + +Use the following format in your internal processing: +Thought: First interpret if the user's input is a casual greeting or an actual search query. +If it seems like a greeting, respond conversationally and suggest some current trending tracks. +If it's a search query, use it directly. + +Action: tool_name (either spotify_search or spotify_trending) +Action Input: input to the tool +Observation: tool's response + +Final Answer: If the input was conversational, start with a greeting before showing the music results. +Then provide results in this format: + +📊 Trending Tracks: +[formatted tracks...] + +🎵 Related Playlists: +[formatted playlists...] +""" + +# Create ReAct Agent function +def create_react_agent(model_name: str): + llm = ChatOpenAI( + base_url=model_service, + api_key="sk-no-key-required", + model=model_name, + streaming=True + ) + + # Create both tools + playlist_tool = SpotifySearchTool() + trending_tool = SpotifyTrendingTool() + + prompt = ChatPromptTemplate.from_messages([ + ("system", REACT_PROMPT), + ("human", "{input}") + ]) + + chain = prompt | llm + + return chain, [playlist_tool, trending_tool] + +#Streamlit +st.title("🎵 Spotify Playlist Explorer") + +if "spotify_credentials_set" not in st.session_state: + st.session_state.spotify_credentials_set = False + +# Spotify Credentials Management in Sidebar +with st.sidebar: + st.markdown("### Spotify Credentials") + + # Check if credentials exist in environment variables + env_credentials_exist = bool(os.getenv("SPOTIFY_CLIENT_ID")) and bool(os.getenv("SPOTIFY_CLIENT_SECRET")) + + if not env_credentials_exist: + st.warning("Spotify credentials not found in environment variables.") + + # Initialize session state for credentials + if "spotify_client_id" not in st.session_state: + st.session_state.spotify_client_id = "" + if "spotify_client_secret" not in st.session_state: + st.session_state.spotify_client_secret = "" + + # Input fields for credentials + client_id = st.text_input( + "Enter Spotify Client ID", + value=st.session_state.spotify_client_id, + type="password" + ) + client_secret = st.text_input( + "Enter Spotify Client Secret", + value=st.session_state.spotify_client_secret, + type="password" + ) + + if st.button("Save Credentials"): + st.session_state.spotify_client_id = client_id + st.session_state.spotify_client_secret = client_secret + st.session_state.spotify_credentials_set = True + st.success("Credentials saved!") + st.rerun() + else: + st.success("Using credentials from environment variables") + st.session_state.spotify_credentials_set = True + +# Check if credentials are available before proceeding +credentials_available = env_credentials_exist or st.session_state.spotify_credentials_set + +if not credentials_available: + st.error("Please provide Spotify credentials in the sidebar to continue.") +else: + + with st.spinner("Checking Model Service Availability..."): + server = checking_model_service() + + model_name = os.getenv("MODEL_NAME", "") + if server == "Ollama": + with st.sidebar: + model_name = st.radio( + label="Select Model", + options=get_models() + ) + + try: + agent, tools = create_react_agent(model_name) + playlist_tool, trending_tool = tools + + if "messages" not in st.session_state: + st.session_state.messages = [] + + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + if prompt := st.chat_input("What kind of playlists are you looking for?"): + st.session_state.messages.append({"role": "user", "content": prompt}) + + with st.chat_message("user"): + st.markdown(prompt) + + with st.chat_message("assistant"): + try: + tool_responses = { + "playlists": playlist_tool._run(prompt), + "trending": trending_tool._run(prompt) + } + + agent_response = agent.invoke({ + "input": prompt, + "tools": [tool.description for tool in tools], + "query": prompt, + "observation": tool_responses, + "answer": "Based on the search results, here's what I found:" + }) + + with st.expander("See thinking process"): + st.markdown(agent_response.content) + + formatted_response = format_spotify_response(tool_responses) + st.markdown(formatted_response) + + st.session_state.messages.append({ + "role": "assistant", + "content": formatted_response + }) + except Exception as e: + error_message = f"Error processing request: {str(e)}" + st.error(error_message) + except Exception as e: + st.error(f"Error initializing Spotify API: {str(e)}") \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/agents/app/requirements.txt b/chatbot/recipes/natural_language_processing/agents/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..7690cacca8e98152d49bb2eb54ea5f8de711d4e5 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/agents/app/requirements.txt @@ -0,0 +1,10 @@ +streamlit>=1.24.0 +langchain-core>=0.1.0 +langchain-openai>=0.0.5 +python-dotenv>=0.19.0 + +requests>=2.31.0 + +typing-extensions>=4.5.0 + +streamlit-chat>=0.1.1 diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/README.md b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b1bd6f2727e16dc5189373041d6de24150e203ad --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/README.md @@ -0,0 +1,9 @@ +# Java-based chatbot application - Quarkus + +This application implements a simple chatbot backed by Quarkus and its +LangChain4j extension. The UI communicates with the backend application via +web sockets and the backend uses the OpenAI API to talk to the model served +by Podman AI Lab. + +Documentation for Quarkus+LangChain4j can be found at +https://docs.quarkiverse.io/quarkus-langchain4j/dev/. diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/ai-lab.yaml b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9c5122522bd57a170aca1b1269c803bc2d749c04 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/ai-lab.yaml @@ -0,0 +1,26 @@ +version: v1.0 +application: + type: language + name: ChatBot_Java_Quarkus + description: Chatbot sample based on Quarkus + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: quarkus-chat-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8080 \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/.mvn/wrapper/maven-wrapper.properties b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000000000000000000000000000000000000..d58dfb70bab565a697e6854eb012d17e0fd39bd4 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/Containerfile b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..a97b04688636c4ed9eee4fb4fa826500a16cba6e --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/Containerfile @@ -0,0 +1,7 @@ +FROM registry.access.redhat.com/ubi8/openjdk-21:latest +WORKDIR /app +COPY --chown=185:0 --chmod=744 . . +RUN mvn package +EXPOSE 8080 +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/app/target/quarkus-app/quarkus-run.jar" diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw new file mode 100644 index 0000000000000000000000000000000000000000..19529ddf8c6eaa08c5c75ff80652d21ce4b72f8c --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw.cmd b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw.cmd new file mode 100644 index 0000000000000000000000000000000000000000..249bdf3822221aa612d1da2605316cabd7b07e50 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/pom.xml b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..2f1145112105b2f95fcc572be726cc8493b1b021 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/pom.xml @@ -0,0 +1,138 @@ + + + 4.0.0 + + org.example + podman-ai-sample-chatbot-quarkus + Quarkus-based chatbot sample + 1.0-SNAPSHOT + + + 3.13.0 + true + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.15.1 + true + 3.2.5 + 0.21.0 + + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + + io.quarkus + quarkus-rest-jackson + + + io.quarkus + quarkus-websockets-next + + + io.quarkiverse.langchain4j + quarkus-langchain4j-openai + ${quarkus-langchain4j.version} + + + + + io.mvnpm + importmap + 1.0.11 + + + org.mvnpm + lit + 3.2.0 + runtime + + + org.mvnpm + wc-chatbot + 0.2.0 + runtime + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.platform.version} + + + + build + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + maven-surefire-plugin + 3.5.0 + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + 3.5.1 + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + native + + + + + diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/Bot.java b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/Bot.java new file mode 100644 index 0000000000000000000000000000000000000000..dd807ec1c227ab37b991dba5dac025cec75e0f85 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/Bot.java @@ -0,0 +1,15 @@ +package io.quarkiverse.langchain4j.sample.chatbot; + +import dev.langchain4j.service.SystemMessage; +import dev.langchain4j.service.UserMessage; +import io.quarkiverse.langchain4j.RegisterAiService; +import io.smallrye.mutiny.Multi; +import jakarta.enterprise.context.SessionScoped; + +@RegisterAiService +@SessionScoped +public interface Bot { + + Multi chat(@UserMessage String question); + +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ChatBotWebSocket.java b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ChatBotWebSocket.java new file mode 100644 index 0000000000000000000000000000000000000000..b79d3668f7dfbc27d224711580ec3c283b00e87b --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ChatBotWebSocket.java @@ -0,0 +1,23 @@ +package io.quarkiverse.langchain4j.sample.chatbot; + +import io.quarkus.websockets.next.OnOpen; +import io.quarkus.websockets.next.OnTextMessage; +import io.quarkus.websockets.next.WebSocket; +import io.smallrye.mutiny.Multi; + +@WebSocket(path = "/chatbot") +public class ChatBotWebSocket { + + private final Bot bot; + + public ChatBotWebSocket(Bot bot) { + this.bot = bot; + } + + @OnTextMessage + public Multi onMessage(String message) { + return bot.chat(message).onFailure().recoverWithItem(t -> + "There was an error in communicating with the model server"); + } + +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ImportmapResource.java b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ImportmapResource.java new file mode 100644 index 0000000000000000000000000000000000000000..88d0ee21da4a0ddd252cbbb6815db95cb4738304 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ImportmapResource.java @@ -0,0 +1,51 @@ +package io.quarkiverse.langchain4j.sample.chatbot; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import io.mvnpm.importmap.Aggregator; + +/** + * Dynamically create the import map + */ +@ApplicationScoped +@Path("/_importmap") +public class ImportmapResource { + private String importmap; + + // See https://github.com/WICG/import-maps/issues/235 + // This does not seem to be supported by browsers yet... + @GET + @Path("/dynamic.importmap") + @Produces("application/importmap+json") + public String importMap() { + return this.importmap; + } + + @GET + @Path("/dynamic-importmap.js") + @Produces("application/javascript") + public String importMapJson() { + return JAVASCRIPT_CODE.formatted(this.importmap); + } + + @PostConstruct + void init() { + Aggregator aggregator = new Aggregator(); + // Add our own mappings + aggregator.addMapping("icons/", "/icons/"); + aggregator.addMapping("components/", "/components/"); + aggregator.addMapping("fonts/", "/fonts/"); + this.importmap = aggregator.aggregateAsJson(); + } + + private static final String JAVASCRIPT_CODE = """ + const im = document.createElement('script'); + im.type = 'importmap'; + im.textContent = JSON.stringify(%s); + document.currentScript.after(im); + """; +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-chat.js b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-chat.js new file mode 100644 index 0000000000000000000000000000000000000000..1b7823bafcde84e173ec0c2240c4d40553cd80c7 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-chat.js @@ -0,0 +1,64 @@ +import {css, LitElement} from 'lit'; + +export class DemoChat extends LitElement { + + _stripHtml(html) { + const div = document.createElement("div"); + div.innerHTML = html; + return div.textContent || div.innerText || ""; + } + + connectedCallback() { + const chatBot = document.getElementsByTagName("chat-bot")[0]; + + const protocol = (window.location.protocol === 'https:') ? 'wss' : 'ws'; + const socket = new WebSocket(protocol + '://' + window.location.host + '/chatbot'); + + const that = this; + socket.onmessage = function (event) { + chatBot.hideLastLoading(); + // LLM response + let lastMessage; + if (chatBot.messages.length > 0) { + lastMessage = chatBot.messages[chatBot.messages.length - 1]; + } + if (lastMessage && lastMessage.sender.name === "Bot" && ! lastMessage.loading) { + if (! lastMessage.msg) { + lastMessage.msg = ""; + } + lastMessage.msg += event.data; + let bubbles = chatBot.shadowRoot.querySelectorAll("chat-bubble"); + let bubble = bubbles.item(bubbles.length - 1); + if (lastMessage.message) { + bubble.innerHTML = that._stripHtml(lastMessage.message) + lastMessage.msg; + } else { + bubble.innerHTML = lastMessage.msg; + } + chatBot.body.scrollTo({ top: chatBot.body.scrollHeight, behavior: 'smooth' }) + } else { + chatBot.sendMessage(event.data, { + right: false, + sender: { + name: "Bot" + } + }); + } + } + + chatBot.addEventListener("sent", function (e) { + if (e.detail.message.sender.name !== "Bot") { + // User message + const msg = that._stripHtml(e.detail.message.message); + socket.send(msg); + chatBot.sendMessage("", { + right: false, + loading: true + }); + } + }); + } + + +} + +customElements.define('demo-chat', DemoChat); \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-title.js b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-title.js new file mode 100644 index 0000000000000000000000000000000000000000..9d12c8c4daa6bd8d6254ecb9572770a9ea069b81 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/components/demo-title.js @@ -0,0 +1,53 @@ +import {LitElement, html, css} from 'lit'; + +export class DemoTitle extends LitElement { + + static styles = css` + h1 { + font-family: "Red Hat Mono", monospace; + font-size: 60px; + font-style: normal; + font-variant: normal; + font-weight: 700; + line-height: 26.4px; + color: var(--main-highlight-text-color); + } + + .title { + text-align: center; + padding: 1em; + background: var(--main-bg-color); + } + + .explanation { + margin-left: auto; + margin-right: auto; + width: 50%; + text-align: justify; + font-size: 20px; + } + + .explanation img { + max-width: 60%; + display: block; + float:left; + margin-right: 2em; + margin-top: 1em; + } + ` + + render() { + return html` +
+

Chatbot

+
+
+ This demo shows how to build a simple chatbot. + Click the red robot button and start chatting. +
+ ` + } + +} + +customElements.define('demo-title', DemoTitle); \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/fonts/red-hat-font.min.css b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/fonts/red-hat-font.min.css new file mode 100644 index 0000000000000000000000000000000000000000..f0301077518b2467cec48b3b9ba1eb42a1a2d7b5 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/fonts/red-hat-font.min.css @@ -0,0 +1 @@ +@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg8z6hR4jNCH5Z.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Display";font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/redhatdisplay/v7/8vIQ7wUr0m80wwYf0QCXZzYzUoTg_T6hR4jNCA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQZqctMc-JPWCN.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQaKctMc-JPQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQZqctMc-JPWCN.woff2) format("woff2");unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Red Hat Text";font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/redhattext/v6/RrQXbohi_ic6B3yVSzGBrMxQaKctMc-JPQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}/*# sourceMappingURL=red-hat-font.css.map */ \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/images/chatbot-architecture.png b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/images/chatbot-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..e3c4d89127ac6d5c9bdd86cd673b3b53ce6a6a2a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/images/chatbot-architecture.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bdb11204dfd48405f5403734f1048c9c4fe16ed242699ad877cb47a9ab2cd0b1 +size 220356 diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/index.html b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000000000000000000000000000000..87005b73eb12b4b614c00e50f0b268340be9a23e --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + ChatBot + + + + + + + + +
+ + + +
+ + + + \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/application.properties b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..8f51dfede1414f979624506407c3e0073098b6fa --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-java-quarkus/app/src/main/resources/application.properties @@ -0,0 +1,3 @@ +quarkus.langchain4j.timeout=120s +quarkus.langchain4j.openai.api-key=NOT-NECESSARY +quarkus.langchain4j.openai.base-url=${MODEL_ENDPOINT}/v1/ \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/Makefile b/chatbot/recipes/natural_language_processing/chatbot-nodejs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9b541ffab251e872371c86917ed684963a6f895f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= chatbot-nodejs +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/README.md b/chatbot/recipes/natural_language_processing/chatbot-nodejs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9d18ba0a7238848a9ce02bdaa58a23845986a893 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/README.md @@ -0,0 +1,180 @@ +# Chat Application + + This recipe helps developers start building their own custom LLM enabled chat applications. It consists of two main components: the Model Service and the AI Application. + + There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + + The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://js.langchain.com/v0.2/docs/introduction/) Typescript package to simplify communication with the Model Service and uses [react](https://react.dev/) and [react-chatbotify](https://react-chatbotify.tjtanjin.com/) for the UI layer. You can find an example of the chat application below. + +![](/assets/chatbot_nodejs_ui.png) + + +## Try the Chat Application + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Chatbot-nodejs` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/chatbot.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/chatbot-nodejs.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `chatbot-nodejs`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead. +Please refer to the section below for more details about [interacting with the chatbot application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop chatbot-nodejs +podman pod rm chatbot-nodejs +``` + +## Download a model + +If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well +performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/chatbot-nodejs +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the chat application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled chatbot applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample chatbot workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the chatbot application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/chatbot-nodejs-bootc:latest +``` + +Upon a reboot, you'll see that the chatbot service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status chatbot-nodejs +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/ai-lab.yaml b/chatbot/recipes/natural_language_processing/chatbot-nodejs/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..63a2444e7ebcbdac46d1564a0e228109584895f6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: ChatBot_nodejs + description: Chat with a model service in a web frontend. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: nodejs-chat-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/chatbot-nodejs:latest diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/.gitignore b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4d29575de80483b005c29bfcac5061cd2f45313e --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/Containerfile b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..829938fab30e349812d2f9d26527f2cd454b7539 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/Containerfile @@ -0,0 +1,56 @@ +# Install dependencies only when needed +FROM registry.access.redhat.com/ubi8/nodejs-18-minimal:1-86 AS deps +USER 0 +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Rebuild the source code only when needed +FROM registry.access.redhat.com/ubi8/nodejs-18-minimal:1-86 AS builder +USER 0 +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +ENV NEXT_TELEMETRY_DISABLED 1 + +# If using npm comment out above and use below instead +RUN npm run build + +# Production image, copy all the files and run next +FROM registry.access.redhat.com/ubi8/nodejs-18-minimal:1-86 AS runner +USER 0 +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to enable telemetry during runtime. +ENV NEXT_TELEMETRY_DISABLED 1 + +#COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown 1001:1001 .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=1001:1001 /app/.next/standalone ./ +COPY --from=builder --chown=1001:1001 /app/.next/static ./.next/static + +USER 1001 + +EXPOSE 8501 + +ENV PORT 8501 + +CMD ["sh", "-c", "HOSTNAME=0.0.0.0 node server"] diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/layout.tsx b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e16a2c20c9730cd7b6dce367717a2e9108accfa --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: 'Sample Node.js AI Chatbot', + description: 'Sample Node.js AI Chatbot', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + {children} + + ) +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/page.js b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/page.js new file mode 100644 index 0000000000000000000000000000000000000000..8bf77277a5194c415ad8da0224632047764f6ec4 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/app/page.js @@ -0,0 +1,63 @@ +'use client'; +import io from 'socket.io-client'; +import { lazy, useEffect, useState } from "react"; +const Chatbot = lazy(() => import("react-chatbotify")); + +function App() { + ///////////////////////////////////// + // chatbotify flow definition + const flow = { + start: { + message: 'How can I help you ?', + path: 'get_question', + }, + get_question: { + message: async (params) => { + return await answerQuestion(params.userInput); + + }, + path: 'get_question' + }, + }; + + ///////////////////////////////////// + // uses socket.io to relay question to back end + // service and return the answer + async function answerQuestion(question) { + return new Promise((resolve, reject) => { + socket.emit('question', question); + socket.on('answer', (answer) => { + resolve(answer); + }); + }); + }; + + ///////////////////////////////////// + // react/next plubming + const [isLoaded, setIsLoaded] = useState(false); + const [socket, setSocket] = useState(undefined); + useEffect(() => { + setIsLoaded(true); + + // Create a socket connection + const socket = io({ path: '/api/socket.io'}); + setSocket(socket); + + // Clean up the socket connection on unmount + return () => { + socket.disconnect(); + }; + }, []) + + return ( + <> + { isLoaded && ( + + )} + + ); +}; + +export default App; diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/next.config.js b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..b9f289635e5ba1565ca7693a190a97b4eb643a29 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +module.exports = { + output: "standalone", +}; diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package-lock.json b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..fed29cfa90970e3dbe742d141016966a37ab6404 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package-lock.json @@ -0,0 +1,19568 @@ +{ + "name": "app", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "version": "0.1.0", + "dependencies": { + "@langchain/community": "^0.2.18", + "@langchain/core": "^0.2.14", + "@langchain/openai": "^0.2.1", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "next": "^14.2.5", + "react": "^18.3.1", + "react-chatbotify": "^1.7.0", + "react-dom": "^18.3.1", + "react-scripts": "5.0.1", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "web-vitals": "^2.1.4" + }, + "devDependencies": { + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.0.3", + "postcss": "^8", + "tailwindcss": "^3.3.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", + "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.8.tgz", + "integrity": "sha512-c4IM7OTg6k1Q+AJ153e2mc2QVTezTwnb4VzquwcyiEzGnW0Kedv4do/TrkU98qPeC5LNiMt/QXwIjzYXLBpyZg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.8.tgz", + "integrity": "sha512-6AWcmZC/MZCO0yKys4uhg5NlxL0ESF3K6IAaoQ+xSXvPyPyxNWRafP+GDbI88Oh68O7QkJgmEtedWPM9U0pZNg==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz", + "integrity": "sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA==", + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.8.tgz", + "integrity": "sha512-47DG+6F5SzOi0uEvK4wMShmn5yY0mVjVJoWTphdY2B4Rx9wHgjK7Yhtr0ru6nE+sn0v38mzrWOlah0p/YlHHOQ==", + "dependencies": { + "@babel/types": "^7.24.8", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz", + "integrity": "sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.8.tgz", + "integrity": "sha512-m4vWKVqvkVAWLXfHCCfff2luJj86U+J0/x+0N3ArG/tP0Fq7zky2dYwMbtPmkc/oulkkbjdL3uWzuoBwQ8R00Q==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "dependencies": { + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.7.tgz", + "integrity": "sha512-RL9GR0pUG5Kc8BUWLNDm2T5OpYwSX15r98I0IkgmRQTXuELq/OynH8xtMTMvTJFjXbMWFVTKtYkTaYQsuAwQlQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-decorators": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.7.tgz", + "integrity": "sha512-Ui4uLJJrRV1lb38zg1yYTmRKmiZLiftDEvZN2iq3kd9kUFU+PttmzTbAFC2ucRk/XJmtek6G23gPsuZbhrT8fQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.7.tgz", + "integrity": "sha512-cjRKJ7FobOH2eakx7Ja+KpJRj8+y+/SiB3ooYm/n2UJfxu0oEaOoxOinitkJcPqv9KxS0kxTGPUaR7L2XcXDXA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-flow": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.7.tgz", + "integrity": "sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", + "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", + "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", + "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.8.tgz", + "integrity": "sha512-CgFgtN61BbdOGCP4fLaAMOPkzWUh6yQZNMr5YSt8uz2cZSSiQONCQFWqsE4NeVfOIhqDOlS9CR3WD91FzMeB2Q==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", + "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.8", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", + "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.24.7", + "@babel/plugin-transform-react-jsx-development": "^7.24.7", + "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" + }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.8.tgz", + "integrity": "sha512-SkSBEHwwJRU52QEVZBmMBnE5Ux2/6WU1grdYyOhpbCNxbmJrDuDCphBzKZSO3taf0zztp+qkWlymE5tVL5l0TA==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" + }, + "node_modules/@csstools/normalize.css": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.1.1.tgz", + "integrity": "sha512-YAYeJ+Xqh7fUou1d1j9XHl44BmsuThiTr4iNrgCQ3J27IbhXsxXDGZ1cXv8Qvs99d4rBbLiSKy3+WZiet32PcQ==" + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-1.1.1.tgz", + "integrity": "sha512-+KdYrpKC5TgomQr2DlZF4lDEpHcoxnj5IGddYYfBWJAKfj1JtuHUIqMa+E1pJJ+z3kvDViWMqyqPlG4Ja7amQA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.2", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-1.1.1.tgz", + "integrity": "sha512-Bc0f62WmHdtRDjf5f3e2STwRAl89N2CLb+9iAwzrv4L2hncrbDwnQD9PCq0gtAt7pOI2leIV08HIBUd4jxD8cw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.1.tgz", + "integrity": "sha512-ZgrlzuUAjXIOc2JueK0X5sZDjCtgimVp/O5CEqTcs5ShWBa6smhWYbS0x5cVc/+rycTDbjjzoP0KTDnUneZGOg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.2.tgz", + "integrity": "sha512-YHdEru4o3Rsbjmu6vHy4UKOXZD+Rn2zmkAmLRfPet6+Jz4Ojw8cbWxe1n42VaXQhD3CQUXXTooIy8OkVbUcL+w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.1.tgz", + "integrity": "sha512-Ot1rcwRAaRHNKC9tAqoqNZhjdYBzKk1POgWfhN4uCOE47ebGcLRqXjKkApVDpjifL6u2/55ekkpnFcp+s/OZUw==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.7.tgz", + "integrity": "sha512-7JPeVVZHd+jxYdULl87lvjgvWldYu+Bc62s9vD/ED6/QTGjy0jy0US/f6BG53sVMTBJ1lzKZFpYmofBN9eaRiA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-1.0.0.tgz", + "integrity": "sha512-JCsQsw1wjYwv1bJmgjKSoZNvf7R6+wuHDAbi5f/7MbFhl2d/+v+TvBTU4BJH3G1X1H87dHl0mh6TfYogbT/dJQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.1.tgz", + "integrity": "sha512-jcOanIbv55OFKQ3sYeFD/T0Ti7AMXc9nM1hZWu8m/2722gOTxFg7xYu4RDLJLeZmPUVQlGzo4jhzvTUq3x4ZUw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.1.1.tgz", + "integrity": "sha512-nJpJgsdA3dA9y5pgyb/UfEzE7W5Ka7u0CX0/HIMVBNWzWemdcTH3XwANECU6anWv/ao4vVNLTMxhiPNZsTK6iA==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz", + "integrity": "sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-1.0.1.tgz", + "integrity": "sha512-dz0LNoo3ijpTOQqEJLY8nyaapl6umbmDcgj4AD0lgVQ572b2eqA1iGZYTTWhrcrHztWDDRAX2DGYyw2VBjvCvQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-1.0.0.tgz", + "integrity": "sha512-c1XwKJ2eMIWrzQenN0XbcfzckOLLJiczqy+YvfGmzoVXd7pT9FfObiSEfzs84bpE/VqfpEuAZ9tCRbZkZxxbdw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-1.0.2.tgz", + "integrity": "sha512-woKaLO///4bb+zZC2s80l+7cm07M7268MsyG3M0ActXXEFi6SuhvriQYcb58iiKGbjwwIU7n45iRLEHypB47Og==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-1.0.2.tgz", + "integrity": "sha512-c8J4roPBILnelAsdLr4XOAR/GsTm0GJi4XpcfvoWk3U6KiTCqiFYc63KhRMQQX35jYMp4Ao8Ij9+IZRgMfJp1g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", + "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss-selector-parser": "^6.0.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz", + "integrity": "sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/console/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/console/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz", + "integrity": "sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/reporters": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^27.5.1", + "jest-config": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-resolve-dependencies": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "jest-watcher": "^27.5.1", + "micromatch": "^4.0.4", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/core/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz", + "integrity": "sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==", + "dependencies": { + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/environment/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz", + "integrity": "sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "@sinonjs/fake-timers": "^8.0.1", + "@types/node": "*", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/fake-timers/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz", + "integrity": "sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/types": "^27.5.1", + "expect": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/globals/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/globals/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz", + "integrity": "sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-haste-map": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^4.0.1", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^8.1.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz", + "integrity": "sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==", + "dependencies": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9", + "source-map": "^0.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/test-result": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz", + "integrity": "sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/test-result/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz", + "integrity": "sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-runtime": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz", + "integrity": "sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==", + "dependencies": { + "@babel/core": "^7.1.0", + "@jest/types": "^27.5.1", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-util": "^27.5.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/transform/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@jest/transform/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@langchain/community": { + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@langchain/community/-/community-0.2.18.tgz", + "integrity": "sha512-UsCB97dMG87giQLniKx4bjv7OnMw2vQeavSt9gqOnGCnfb5IQBAgdjX4SjwFPbVGMz1HQoQKVlNqQ64ozCdgNg==", + "dependencies": { + "@langchain/core": "~0.2.11", + "@langchain/openai": "~0.1.0", + "binary-extensions": "^2.2.0", + "expr-eval": "^2.0.2", + "flat": "^5.0.2", + "js-yaml": "^4.1.0", + "langchain": "0.2.3", + "langsmith": "~0.1.30", + "uuid": "^10.0.0", + "zod": "^3.22.3", + "zod-to-json-schema": "^3.22.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@aws-crypto/sha256-js": "^5.0.0", + "@aws-sdk/client-bedrock-agent-runtime": "^3.583.0", + "@aws-sdk/client-bedrock-runtime": "^3.422.0", + "@aws-sdk/client-dynamodb": "^3.310.0", + "@aws-sdk/client-kendra": "^3.352.0", + "@aws-sdk/client-lambda": "^3.310.0", + "@aws-sdk/client-s3": "^3.310.0", + "@aws-sdk/client-sagemaker-runtime": "^3.310.0", + "@aws-sdk/client-sfn": "^3.310.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/search-documents": "^12.0.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@clickhouse/client": "^0.2.5", + "@cloudflare/ai": "*", + "@datastax/astra-db-ts": "^1.0.0", + "@elastic/elasticsearch": "^8.4.0", + "@getmetal/metal-sdk": "*", + "@getzep/zep-cloud": "^1.0.6", + "@getzep/zep-js": "^0.9.0", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@google-ai/generativelanguage": "*", + "@google-cloud/storage": "^6.10.1 || ^7.7.0", + "@gradientai/nodejs-sdk": "^1.2.0", + "@huggingface/inference": "^2.6.4", + "@langchain/langgraph": "~0.0.26", + "@layerup/layerup-security": "^1.5.12", + "@mendable/firecrawl-js": "^0.0.13", + "@mlc-ai/web-llm": "0.2.46", + "@mozilla/readability": "*", + "@neondatabase/serverless": "*", + "@notionhq/client": "^2.2.10", + "@opensearch-project/opensearch": "*", + "@pinecone-database/pinecone": "*", + "@planetscale/database": "^1.8.0", + "@premai/prem-sdk": "^0.3.25", + "@qdrant/js-client-rest": "^1.8.2", + "@raycast/api": "^1.55.2", + "@rockset/client": "^0.9.1", + "@smithy/eventstream-codec": "^2.0.5", + "@smithy/protocol-http": "^3.0.6", + "@smithy/signature-v4": "^2.0.10", + "@smithy/util-utf8": "^2.0.0", + "@spider-cloud/spider-client": "^0.0.21", + "@supabase/postgrest-js": "^1.1.1", + "@supabase/supabase-js": "^2.10.0", + "@tensorflow-models/universal-sentence-encoder": "*", + "@tensorflow/tfjs-converter": "*", + "@tensorflow/tfjs-core": "*", + "@upstash/ratelimit": "^1.1.3", + "@upstash/redis": "^1.20.6", + "@upstash/vector": "^1.1.1", + "@vercel/kv": "^0.2.3", + "@vercel/postgres": "^0.5.0", + "@writerai/writer-sdk": "^0.40.2", + "@xata.io/client": "^0.28.0", + "@xenova/transformers": "^2.5.4", + "@zilliz/milvus2-sdk-node": ">=2.3.5", + "apify-client": "^2.7.1", + "assemblyai": "^4.0.0", + "better-sqlite3": ">=9.4.0 <12.0.0", + "cassandra-driver": "^4.7.2", + "cborg": "^4.1.1", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "closevector-common": "0.1.3", + "closevector-node": "0.1.6", + "closevector-web": "0.1.6", + "cohere-ai": "*", + "convex": "^1.3.1", + "couchbase": "^4.3.0", + "crypto-js": "^4.2.0", + "d3-dsv": "^2.0.0", + "discord.js": "^14.14.1", + "dria": "^0.0.3", + "duck-duck-scrape": "^2.2.5", + "epub2": "^3.0.1", + "faiss-node": "^0.5.1", + "firebase-admin": "^11.9.0 || ^12.0.0", + "google-auth-library": "*", + "googleapis": "^126.0.1", + "hnswlib-node": "^3.0.0", + "html-to-text": "^9.0.5", + "ignore": "^5.2.0", + "interface-datastore": "^8.2.11", + "ioredis": "^5.3.2", + "it-all": "^3.0.4", + "jsdom": "*", + "jsonwebtoken": "^9.0.2", + "llmonitor": "^0.5.9", + "lodash": "^4.17.21", + "lunary": "^0.6.11", + "mammoth": "^1.6.0", + "mongodb": ">=5.2.0", + "mysql2": "^3.3.3", + "neo4j-driver": "*", + "node-llama-cpp": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "pdf-parse": "1.1.1", + "pg": "^8.11.0", + "pg-copy-streams": "^6.0.5", + "pickleparser": "^0.2.1", + "playwright": "^1.32.1", + "portkey-ai": "^0.1.11", + "puppeteer": "^19.7.2", + "redis": "*", + "replicate": "^0.29.4", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.20", + "typesense": "^1.5.3", + "usearch": "^1.1.1", + "vectordb": "^0.1.4", + "voy-search": "0.6.2", + "weaviate-ts-client": "*", + "web-auth-library": "^1.0.3", + "ws": "^8.14.2", + "youtube-transcript": "^1.0.6", + "youtubei.js": "^9.1.0" + }, + "peerDependenciesMeta": { + "@aws-crypto/sha256-js": { + "optional": true + }, + "@aws-sdk/client-bedrock-agent-runtime": { + "optional": true + }, + "@aws-sdk/client-bedrock-runtime": { + "optional": true + }, + "@aws-sdk/client-dynamodb": { + "optional": true + }, + "@aws-sdk/client-kendra": { + "optional": true + }, + "@aws-sdk/client-lambda": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@azure/search-documents": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@clickhouse/client": { + "optional": true + }, + "@cloudflare/ai": { + "optional": true + }, + "@datastax/astra-db-ts": { + "optional": true + }, + "@elastic/elasticsearch": { + "optional": true + }, + "@getmetal/metal-sdk": { + "optional": true + }, + "@getzep/zep-cloud": { + "optional": true + }, + "@getzep/zep-js": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@google-ai/generativelanguage": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + }, + "@gradientai/nodejs-sdk": { + "optional": true + }, + "@huggingface/inference": { + "optional": true + }, + "@langchain/langgraph": { + "optional": true + }, + "@layerup/layerup-security": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@mlc-ai/web-llm": { + "optional": true + }, + "@mozilla/readability": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@opensearch-project/opensearch": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@premai/prem-sdk": { + "optional": true + }, + "@qdrant/js-client-rest": { + "optional": true + }, + "@raycast/api": { + "optional": true + }, + "@rockset/client": { + "optional": true + }, + "@smithy/eventstream-codec": { + "optional": true + }, + "@smithy/protocol-http": { + "optional": true + }, + "@smithy/signature-v4": { + "optional": true + }, + "@smithy/util-utf8": { + "optional": true + }, + "@spider-cloud/spider-client": { + "optional": true + }, + "@supabase/postgrest-js": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@tensorflow-models/universal-sentence-encoder": { + "optional": true + }, + "@tensorflow/tfjs-converter": { + "optional": true + }, + "@tensorflow/tfjs-core": { + "optional": true + }, + "@upstash/ratelimit": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@upstash/vector": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@writerai/writer-sdk": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "@xenova/transformers": { + "optional": true + }, + "@zilliz/milvus2-sdk-node": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "cassandra-driver": { + "optional": true + }, + "cborg": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "closevector-common": { + "optional": true + }, + "closevector-node": { + "optional": true + }, + "closevector-web": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "convex": { + "optional": true + }, + "couchbase": { + "optional": true + }, + "crypto-js": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "discord.js": { + "optional": true + }, + "dria": { + "optional": true + }, + "duck-duck-scrape": { + "optional": true + }, + "epub2": { + "optional": true + }, + "faiss-node": { + "optional": true + }, + "firebase-admin": { + "optional": true + }, + "google-auth-library": { + "optional": true + }, + "googleapis": { + "optional": true + }, + "hnswlib-node": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "interface-datastore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "it-all": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "jsonwebtoken": { + "optional": true + }, + "llmonitor": { + "optional": true + }, + "lodash": { + "optional": true + }, + "lunary": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "neo4j-driver": { + "optional": true + }, + "node-llama-cpp": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-copy-streams": { + "optional": true + }, + "pickleparser": { + "optional": true + }, + "playwright": { + "optional": true + }, + "portkey-ai": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "redis": { + "optional": true + }, + "replicate": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "typesense": { + "optional": true + }, + "usearch": { + "optional": true + }, + "vectordb": { + "optional": true + }, + "voy-search": { + "optional": true + }, + "weaviate-ts-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtube-transcript": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/@langchain/community/node_modules/@langchain/openai": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.1.3.tgz", + "integrity": "sha512-riv/JC9x2A8b7GcHu8sx+mlZJ8KAwSSi231IPTlcciYnKozmrQ5H0vrtiD31fxiDbaRsk7tyCpkSBIOQEo7CyQ==", + "dependencies": { + "@langchain/core": ">=0.2.5 <0.3.0", + "js-tiktoken": "^1.0.12", + "openai": "^4.49.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/core": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.2.15.tgz", + "integrity": "sha512-L096itIBQ5XNsy5BCCPqIQEk/x4rzI+U4BhYT+fDBYtljESshIi/WzXdmiGfY/6MpVjB76jNuaRgMDmo1m9NeQ==", + "dependencies": { + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "~0.1.30", + "ml-distance": "^4.0.0", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/openai": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.2.1.tgz", + "integrity": "sha512-Ti3C6ZIUPaueIPAfMljMnLu3GSGNq5KmrlHeWkIbrLShOBlzj4xj7mRfR73oWgAC0qivfxdkfbB0e+WCY+oRJw==", + "dependencies": { + "@langchain/core": ">=0.2.8 <0.3.0", + "js-tiktoken": "^1.0.12", + "openai": "^4.49.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.0.3.tgz", + "integrity": "sha512-cXWgKE3sdWLSqAa8ykbCcUsUF1Kyr5J3HOWYGuobhPEycXW4WI++d5DhzdpL238mzoEXTi90VqfSCra37l5YqA==", + "dependencies": { + "@langchain/core": ">0.2.0 <0.3.0", + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==" + }, + "node_modules/@next/env": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", + "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.0.3.tgz", + "integrity": "sha512-j4K0n+DcmQYCVnSAM+UByTVfIHnYQy2ODozfQP+4RdwtRDfobrIvKq1K4Exb2koJ79HSSa7s6B2SA8T/1YR3RA==", + "dev": true, + "dependencies": { + "glob": "7.1.7" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz", + "integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "@types/resolve": "1.17.1", + "builtin-modules": "^3.1.0", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", + "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", + "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz", + "integrity": "sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz", + "integrity": "sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz", + "integrity": "sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz", + "integrity": "sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz", + "integrity": "sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz", + "integrity": "sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz", + "integrity": "sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-5.5.0.tgz", + "integrity": "sha512-4FiXBjvQ+z2j7yASeGPEi8VD/5rrGQk4Xrq3EdJmoZgz/tpqChpo5hgXDvmEauwtvOc52q8ghhZK4Oy7qph4ig==", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-attribute": "^5.4.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "^5.0.1", + "@svgr/babel-plugin-replace-jsx-attribute-value": "^5.0.1", + "@svgr/babel-plugin-svg-dynamic-title": "^5.4.0", + "@svgr/babel-plugin-svg-em-dimensions": "^5.4.0", + "@svgr/babel-plugin-transform-react-native-svg": "^5.4.0", + "@svgr/babel-plugin-transform-svg-component": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-5.5.0.tgz", + "integrity": "sha512-q52VOcsJPvV3jO1wkPtzTuKlvX7Y3xIcWRpCMtBF3MrteZJtBfQw/+u0B1BHy5ColpQc1/YVTrPEtSYIMNZlrQ==", + "dependencies": { + "@svgr/plugin-jsx": "^5.5.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz", + "integrity": "sha512-cAaR/CAiZRB8GP32N+1jocovUtvlj0+e65TB50/6Lcime+EA49m/8l+P2ko+XPJ4dw3xaPS3jOL4F2X4KWxoeQ==", + "dependencies": { + "@babel/types": "^7.12.6" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz", + "integrity": "sha512-V/wVh33j12hGh05IDg8GpIUXbjAPnTdPTKuP4VNLggnwaHMPNQNae2pRnyTAILWCQdz5GyMqtO488g7CKM8CBA==", + "dependencies": { + "@babel/core": "^7.12.3", + "@svgr/babel-preset": "^5.5.0", + "@svgr/hast-util-to-babel-ast": "^5.5.0", + "svg-parser": "^2.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz", + "integrity": "sha512-r5swKk46GuQl4RrVejVwpeeJaydoxkdwkM1mBKOgJLBUJPGaLci6ylg/IjhrRsREKDkr4kbMWdgOtbXEh0fyLQ==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "deepmerge": "^4.2.2", + "svgo": "^1.2.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/webpack": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-5.5.0.tgz", + "integrity": "sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/plugin-transform-react-constant-elements": "^7.12.1", + "@babel/preset-env": "^7.12.1", + "@babel/preset-react": "^7.12.5", + "@svgr/core": "^5.5.0", + "@svgr/plugin-jsx": "^5.5.0", + "@svgr/plugin-svgo": "^5.5.0", + "loader-utils": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.1.tgz", + "integrity": "sha512-q/WL+vlXMpC0uXDyfsMtc1rmotzLV8Y0gq6q1gfrrDjQeHoeLrqHbxdPvPNAh1i+xuJl7+BezywcXArz7vLqKQ==", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", + "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", + "dependencies": { + "@adobe/css-tools": "^4.0.1", + "@babel/runtime": "^7.9.2", + "@types/testing-library__jest-dom": "^5.9.1", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.5.6", + "lodash": "^4.17.15", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/@testing-library/react": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", + "integrity": "sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@testing-library/dom": "^8.5.0", + "@types/react-dom": "^18.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@testing-library/react/node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@testing-library/react/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/@testing-library/user-event": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", + "integrity": "sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@types/jest/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/q": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.8.tgz", + "integrity": "sha512-hroOstUScF6zhIi+5+x0dzqrHA1EJi+Irri6b1fxolMTqqHIV/Cg77EtnQcZqZCu8hR3mX2BzIxN4/GzI68Kfw==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", + "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" + }, + "node_modules/@types/testing-library__jest-dom": { + "version": "5.14.9", + "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", + "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", + "dependencies": { + "@types/jest": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.7.tgz", + "integrity": "sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-array-method-boxes-properly": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==" + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/babel-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", + "integrity": "sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==", + "dependencies": { + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-jest/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", + "dependencies": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 8.9" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "webpack": ">=2" + } + }, + "node_modules/babel-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz", + "integrity": "sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-named-asset-import": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz", + "integrity": "sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q==", + "peerDependencies": { + "@babel/core": "^7.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==" + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz", + "integrity": "sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==", + "dependencies": { + "babel-plugin-jest-hoist": "^27.5.1", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==" + }, + "node_modules/bfj": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz", + "integrity": "sha512-I6MMLkn+anzNdCUp9hMRyui1HaNEUCco50lxbvNS4+EyXg8lN3nJ48PjPWtbH8UVS9CuMoaKE9U2V3l29DaRQw==", + "dependencies": { + "bluebird": "^3.7.2", + "check-types": "^11.2.3", + "hoopy": "^0.1.4", + "jsonpath": "^1.1.1", + "tryer": "^1.0.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-search": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz", + "integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001641", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz", + "integrity": "sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/case-sensitive-paths-webpack-plugin": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", + "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/check-types": { + "version": "11.2.3", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", + "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", + "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==" + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "dependencies": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/coa/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/coa/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/coa/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==" + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==" + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/css-blank-pseudo": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz", + "integrity": "sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-blank-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz", + "integrity": "sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "bin": { + "css-has-pseudo": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", + "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", + "dependencies": { + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", + "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", + "bin": { + "css-prefers-color-scheme": "dist/cli.cjs" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "node_modules/css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "dependencies": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==" + }, + "node_modules/cssdb": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.11.2.tgz", + "integrity": "sha512-lhQ32TFkc1X4eTefGfYPvgovRSzIMofHkigfH8nWtyRL4XJLsRhJFreRvEgKzept7x1rjBuy3J/MurXLaFxW/A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ] + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/csso/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==" + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" + }, + "node_modules/detect-port-alt": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz", + "integrity": "sha512-5tQykt+LqfJFBEYaDITx7S7cR7mJ/zQmLXZ2qt5w04ainYZw6tBf9dBunMjVeVOdYVRUzUOE4HkY5J7+uttb5Q==", + "dependencies": { + "address": "^1.0.1", + "debug": "^2.6.0" + }, + "bin": { + "detect": "bin/detect-port", + "detect-port": "bin/detect-port" + }, + "engines": { + "node": ">= 4.2.1" + } + }, + "node_modules/detect-port-alt/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/detect-port-alt/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==" + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.827", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.827.tgz", + "integrity": "sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ==" + }, + "node_modules/emittery": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", + "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-next": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.0.3.tgz", + "integrity": "sha512-IKPhpLdpSUyKofmsXUfrvBC49JMUTdeaD8ZIH4v9Vk0sC1X6URTuTJCLtA0Vwuj7V/CQh0oISuSTvNn5//Buew==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "14.0.3", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", + "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.9.1", + "axobject-query": "~3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", + "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz", + "integrity": "sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w==", + "dependencies": { + "@types/eslint": "^7.29.0 || ^8.4.1", + "jest-worker": "^28.0.2", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/jest-worker": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", + "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/eslint-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/expr-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz", + "integrity": "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg==" + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.3.tgz", + "integrity": "sha512-SbH/l9ikmMWycd5puHJKTkZJKddF4iRLyW3DeZ08HTI7NGyLS38MXd/KGgeWumQO7YNQbW2u/NtPT2YowbPaGQ==", + "dependencies": { + "@babel/code-frame": "^7.8.3", + "@types/json-schema": "^7.0.5", + "chalk": "^4.1.0", + "chokidar": "^3.4.2", + "cosmiconfig": "^6.0.0", + "deepmerge": "^4.2.2", + "fs-extra": "^9.0.0", + "glob": "^7.1.6", + "memfs": "^3.1.2", + "minimatch": "^3.0.4", + "schema-utils": "2.7.0", + "semver": "^7.3.2", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=10", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "eslint": ">= 6", + "typescript": ">= 2.7", + "vue-template-compiler": "*", + "webpack": ">= 4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "vue-template-compiler": { + "optional": true + } + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/schema-utils": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", + "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "dependencies": { + "@types/json-schema": "^7.0.4", + "ajv": "^6.12.2", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==" + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==" + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoopy": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", + "integrity": "sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-any-array": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz", + "integrity": "sha512-UtilS7hLRu++wb/WBAw9bNuP1Eg04Ivn1vERJck8zJthEvXCBEBpGR/33u/xLKWEQf95803oalHrVDptcAvFdQ==" + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==" + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-root": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", + "integrity": "sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz", + "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", + "dependencies": { + "@jest/core": "^27.5.1", + "import-local": "^3.0.2", + "jest-cli": "^27.5.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz", + "integrity": "sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==", + "dependencies": { + "@jest/types": "^27.5.1", + "execa": "^5.0.0", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-circus": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.5.1.tgz", + "integrity": "sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-circus/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-circus/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz", + "integrity": "sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==", + "dependencies": { + "@jest/core": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "prompts": "^2.0.1", + "yargs": "^16.2.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-cli/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-cli/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz", + "integrity": "sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==", + "dependencies": { + "@babel/core": "^7.8.0", + "@jest/test-sequencer": "^27.5.1", + "@jest/types": "^27.5.1", + "babel-jest": "^27.5.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.9", + "jest-circus": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-jasmine2": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runner": "^27.5.1", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "peerDependencies": { + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-config/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-config/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/jest-docblock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz", + "integrity": "sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz", + "integrity": "sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-each/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-each/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz", + "integrity": "sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1", + "jsdom": "^16.6.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz", + "integrity": "sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "jest-mock": "^27.5.1", + "jest-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-environment-node/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-environment-node/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", + "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^27.5.1", + "jest-serializer": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "micromatch": "^4.0.4", + "walker": "^1.0.7" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-haste-map/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-haste-map/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-haste-map/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz", + "integrity": "sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^27.5.1", + "is-generator-fn": "^2.0.0", + "jest-each": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "pretty-format": "^27.5.1", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-jasmine2/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-jasmine2/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz", + "integrity": "sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==", + "dependencies": { + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-leak-detector/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/jest-mock": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz", + "integrity": "sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-mock/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", + "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz", + "integrity": "sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==", + "dependencies": { + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^27.5.1", + "jest-validate": "^27.5.1", + "resolve": "^1.20.0", + "resolve.exports": "^1.1.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz", + "integrity": "sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-snapshot": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve-dependencies/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-resolve/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-resolve/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-resolve/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz", + "integrity": "sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==", + "dependencies": { + "@jest/console": "^27.5.1", + "@jest/environment": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.8.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^27.5.1", + "jest-environment-jsdom": "^27.5.1", + "jest-environment-node": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-leak-detector": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-runtime": "^27.5.1", + "jest-util": "^27.5.1", + "jest-worker": "^27.5.1", + "source-map-support": "^0.5.6", + "throat": "^6.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runner/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runner/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz", + "integrity": "sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==", + "dependencies": { + "@jest/environment": "^27.5.1", + "@jest/fake-timers": "^27.5.1", + "@jest/globals": "^27.5.1", + "@jest/source-map": "^27.5.1", + "@jest/test-result": "^27.5.1", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "execa": "^5.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-mock": "^27.5.1", + "jest-regex-util": "^27.5.1", + "jest-resolve": "^27.5.1", + "jest-snapshot": "^27.5.1", + "jest-util": "^27.5.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-runtime/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-runtime/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-serializer": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", + "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", + "dependencies": { + "@types/node": "*", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz", + "integrity": "sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==", + "dependencies": { + "@babel/core": "^7.7.2", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.0.0", + "@jest/transform": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^27.5.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-haste-map": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1", + "jest-util": "^27.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^27.5.1", + "semver": "^7.3.2" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-snapshot/node_modules/diff-sequences": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz", + "integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/expect": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz", + "integrity": "sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==", + "dependencies": { + "@jest/types": "^27.5.1", + "jest-get-type": "^27.5.1", + "jest-matcher-utils": "^27.5.1", + "jest-message-util": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-diff": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz", + "integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-matcher-utils": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz", + "integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^27.5.1", + "jest-get-type": "^27.5.1", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-message-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz", + "integrity": "sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^27.5.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^27.5.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz", + "integrity": "sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==", + "dependencies": { + "@jest/types": "^27.5.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^27.5.1", + "leven": "^3.1.0", + "pretty-format": "^27.5.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-validate/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-validate/node_modules/jest-get-type": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz", + "integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==", + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watch-typeahead": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-1.1.0.tgz", + "integrity": "sha512-Va5nLSJTN7YFtC2jd+7wsoe1pNe5K4ShLux/E5iHEwlB9AxaxmggY7to9KUqKojhaJw3aXqt5WAb4jGPOolpEw==", + "dependencies": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "jest-regex-util": "^28.0.0", + "jest-watcher": "^28.0.0", + "slash": "^4.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "dependencies": { + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + }, + "node_modules/jest-watch-typeahead/node_modules/emittery": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", + "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", + "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^28.1.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-regex-util": { + "version": "28.0.2", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", + "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-util": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", + "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "dependencies": { + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", + "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "dependencies": { + "@jest/test-result": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.10.2", + "jest-util": "^28.1.3", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-watch-typeahead/node_modules/jest-watcher/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watch-typeahead/node_modules/pretty-format": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", + "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "dependencies": { + "@jest/schemas": "^28.1.3", + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/jest-watch-typeahead/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length/node_modules/char-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.1.tgz", + "integrity": "sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watcher": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz", + "integrity": "sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==", + "dependencies": { + "@jest/test-result": "^27.5.1", + "@jest/types": "^27.5.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "jest-util": "^27.5.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@jest/types": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", + "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-watcher/node_modules/@types/yargs": { + "version": "16.0.9", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz", + "integrity": "sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/jest-watcher/node_modules/jest-util": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", + "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", + "dependencies": { + "@jest/types": "^27.5.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.12.tgz", + "integrity": "sha512-L7wURW1fH9Qaext0VzaUDpFGVQgjkdE3Dgsy9/+yXyGEpBKnylTd0mU0bfbNkKDlXRb6TEsZkwuflu1B8uQbJQ==", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "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 + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/langchain": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.2.3.tgz", + "integrity": "sha512-T9xR7zd+Nj0oXy6WoYKmZLy0DlQiDLFPGYWdOXDxy+AvqlujoPdVQgDSpdqiOHvAjezrByAoKxoHCz5XMwTP/Q==", + "dependencies": { + "@langchain/core": "~0.2.0", + "@langchain/openai": "~0.0.28", + "@langchain/textsplitters": "~0.0.0", + "binary-extensions": "^2.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langchainhub": "~0.0.8", + "langsmith": "~0.1.7", + "ml-distance": "^4.0.0", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^9.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.310.0", + "@aws-sdk/client-sagemaker-runtime": "^3.310.0", + "@aws-sdk/client-sfn": "^3.310.0", + "@aws-sdk/credential-provider-node": "^3.388.0", + "@azure/storage-blob": "^12.15.0", + "@browserbasehq/sdk": "*", + "@gomomento/sdk": "^1.51.1", + "@gomomento/sdk-core": "^1.51.1", + "@gomomento/sdk-web": "^1.51.1", + "@mendable/firecrawl-js": "^0.0.13", + "@notionhq/client": "^2.2.10", + "@pinecone-database/pinecone": "*", + "@supabase/supabase-js": "^2.10.0", + "@vercel/kv": "^0.2.3", + "@xata.io/client": "^0.28.0", + "apify-client": "^2.7.1", + "assemblyai": "^4.0.0", + "axios": "*", + "cheerio": "^1.0.0-rc.12", + "chromadb": "*", + "convex": "^1.3.1", + "couchbase": "^4.3.0", + "d3-dsv": "^2.0.0", + "epub2": "^3.0.1", + "fast-xml-parser": "*", + "handlebars": "^4.7.8", + "html-to-text": "^9.0.5", + "ignore": "^5.2.0", + "ioredis": "^5.3.2", + "jsdom": "*", + "mammoth": "^1.6.0", + "mongodb": ">=5.2.0", + "node-llama-cpp": "*", + "notion-to-md": "^3.1.0", + "officeparser": "^4.0.4", + "pdf-parse": "1.1.1", + "peggy": "^3.0.2", + "playwright": "^1.32.1", + "puppeteer": "^19.7.2", + "pyodide": "^0.24.1", + "redis": "^4.6.4", + "sonix-speech-recognition": "^2.1.1", + "srt-parser-2": "^1.2.3", + "typeorm": "^0.3.12", + "weaviate-ts-client": "*", + "web-auth-library": "^1.0.3", + "ws": "^8.14.2", + "youtube-transcript": "^1.0.6", + "youtubei.js": "^9.1.0" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-s3": { + "optional": true + }, + "@aws-sdk/client-sagemaker-runtime": { + "optional": true + }, + "@aws-sdk/client-sfn": { + "optional": true + }, + "@aws-sdk/credential-provider-node": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@browserbasehq/sdk": { + "optional": true + }, + "@gomomento/sdk": { + "optional": true + }, + "@gomomento/sdk-core": { + "optional": true + }, + "@gomomento/sdk-web": { + "optional": true + }, + "@mendable/firecrawl-js": { + "optional": true + }, + "@notionhq/client": { + "optional": true + }, + "@pinecone-database/pinecone": { + "optional": true + }, + "@supabase/supabase-js": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "apify-client": { + "optional": true + }, + "assemblyai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "chromadb": { + "optional": true + }, + "convex": { + "optional": true + }, + "couchbase": { + "optional": true + }, + "d3-dsv": { + "optional": true + }, + "epub2": { + "optional": true + }, + "faiss-node": { + "optional": true + }, + "fast-xml-parser": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "html-to-text": { + "optional": true + }, + "ignore": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "mammoth": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "node-llama-cpp": { + "optional": true + }, + "notion-to-md": { + "optional": true + }, + "officeparser": { + "optional": true + }, + "pdf-parse": { + "optional": true + }, + "peggy": { + "optional": true + }, + "playwright": { + "optional": true + }, + "puppeteer": { + "optional": true + }, + "pyodide": { + "optional": true + }, + "redis": { + "optional": true + }, + "sonix-speech-recognition": { + "optional": true + }, + "srt-parser-2": { + "optional": true + }, + "typeorm": { + "optional": true + }, + "weaviate-ts-client": { + "optional": true + }, + "web-auth-library": { + "optional": true + }, + "ws": { + "optional": true + }, + "youtube-transcript": { + "optional": true + }, + "youtubei.js": { + "optional": true + } + } + }, + "node_modules/langchain/node_modules/@langchain/openai": { + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.0.34.tgz", + "integrity": "sha512-M+CW4oXle5fdoz2T2SwdOef8pl3/1XmUx1vjn2mXUVM/128aO0l23FMF0SNBsAbRV6P+p/TuzjodchJbi0Ht/A==", + "dependencies": { + "@langchain/core": ">0.1.56 <0.3.0", + "js-tiktoken": "^1.0.12", + "openai": "^4.41.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/langchain/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/langchainhub": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/langchainhub/-/langchainhub-0.0.11.tgz", + "integrity": "sha512-WnKI4g9kU2bHQP136orXr2bcRdgz9iiTBpTN0jWt9IlScUKnJBoD0aa2HOzHURQKeQDnt2JwqVmQ6Depf5uDLQ==" + }, + "node_modules/langsmith": { + "version": "0.1.36", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.1.36.tgz", + "integrity": "sha512-D5hhkFl31uxFdffx0lA6pin0lt8Pv2dpHFZYpSgEzvQ26PQ/Y/tnniQ+aCNokIXuLhMa7uqLtb6tfwjfiZXgdg==", + "dependencies": { + "@types/uuid": "^9.0.1", + "commander": "^10.0.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "@langchain/core": "*", + "langchain": "*", + "openai": "*" + }, + "peerDependenciesMeta": { + "@langchain/core": { + "optional": true + }, + "langchain": { + "optional": true + }, + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/launch-editor": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.0.tgz", + "integrity": "sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA==", + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ml-array-mean": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ml-array-mean/-/ml-array-mean-1.1.6.tgz", + "integrity": "sha512-MIdf7Zc8HznwIisyiJGRH9tRigg3Yf4FldW8DxKxpCCv/g5CafTw0RRu51nojVEOXuCQC7DRVVu5c7XXO/5joQ==", + "dependencies": { + "ml-array-sum": "^1.1.6" + } + }, + "node_modules/ml-array-sum": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/ml-array-sum/-/ml-array-sum-1.1.6.tgz", + "integrity": "sha512-29mAh2GwH7ZmiRnup4UyibQZB9+ZLyMShvt4cH4eTK+cL2oEMIZFnSyB3SS8MlsTh6q/w/yh48KmqLxmovN4Dw==", + "dependencies": { + "is-any-array": "^2.0.0" + } + }, + "node_modules/ml-distance": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/ml-distance/-/ml-distance-4.0.1.tgz", + "integrity": "sha512-feZ5ziXs01zhyFUUUeZV5hwc0f5JW0Sh0ckU1koZe/wdVkJdGxcP06KNQuF0WBTj8FttQUzcvQcpcrOp/XrlEw==", + "dependencies": { + "ml-array-mean": "^1.1.6", + "ml-distance-euclidean": "^2.0.0", + "ml-tree-similarity": "^1.0.0" + } + }, + "node_modules/ml-distance-euclidean": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ml-distance-euclidean/-/ml-distance-euclidean-2.0.0.tgz", + "integrity": "sha512-yC9/2o8QF0A3m/0IXqCTXCzz2pNEzvmcE/9HFKOZGnTjatvBbsn4lWYJkxENkA4Ug2fnYl7PXQxnPi21sgMy/Q==" + }, + "node_modules/ml-tree-similarity": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ml-tree-similarity/-/ml-tree-similarity-1.0.0.tgz", + "integrity": "sha512-XJUyYqjSuUQkNQHMscr6tcjldsOoAekxADTplt40QKfwW6nd++1wHWV9AArl0Zvw/TIHgNaZZNvr8QGvE8wLRg==", + "dependencies": { + "binary-search": "^1.3.5", + "num-sort": "^2.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", + "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", + "dependencies": { + "@next/env": "14.2.5", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.5", + "@next/swc-darwin-x64": "14.2.5", + "@next/swc-linux-arm64-gnu": "14.2.5", + "@next/swc-linux-arm64-musl": "14.2.5", + "@next/swc-linux-x64-gnu": "14.2.5", + "@next/swc-linux-x64-musl": "14.2.5", + "@next/swc-win32-arm64-msvc": "14.2.5", + "@next/swc-win32-ia32-msvc": "14.2.5", + "@next/swc-win32-x64-msvc": "14.2.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/num-sort": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/num-sort/-/num-sort-2.1.0.tgz", + "integrity": "sha512-1MQz1Ed8z2yckoBeSfkQHHO9K1yDRxxtotKSJ9yvcTUUxSvfvzEq5GwBrjjHEpMlq/k5gvXdmJ1SbYxWtpNoVg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.8.tgz", + "integrity": "sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==", + "dependencies": { + "array.prototype.reduce": "^1.0.6", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "gopd": "^1.0.1", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "4.52.7", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.52.7.tgz", + "integrity": "sha512-dgxA6UZHary6NXUHEDj5TWt8ogv0+ibH+b4pT5RrWMjiRZVylNwLcw/2ubDrX5n0oUmHX/ZgudMJeemxzOvz7A==", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7", + "web-streams-polyfill": "^3.2.1" + }, + "bin": { + "openai": "bin/cli" + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", + "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.2.tgz", + "integrity": "sha512-XIidXV8fDr0kKt28vqki84fRK8VW8eTuIa4PChv2MqKuT6C9UjmSKzen6KaWhWEoYvwxFCa7n/tC1SZ3tyq4SQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-browser-comments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-browser-comments/-/postcss-browser-comments-4.0.0.tgz", + "integrity": "sha512-X9X9/WN3KIvY9+hNERUqX9gncsgBA25XaeR+jshHz2j8+sYyHktHw1JdKuMjeLpGktXidqDhA7b/qm1mrBDmgg==", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "browserslist": ">=4", + "postcss": ">=8" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.4.tgz", + "integrity": "sha512-2yrTAUZUab9s6CpxkxC4rVgFEVaR6/2Pipvi6qcgvnYiVqZcbDHEoBDhrXzyb7Efh2CCfHQNtcqWcIruDTIUeg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.4.tgz", + "integrity": "sha512-nLo2DCRC9eE4w2JmuKgVA3fGL3d01kGq752pVALF68qpGLmx2Qrk91QTKkdUqqp45T1K1XV8IhQpcu1hoAQflQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.1.1.tgz", + "integrity": "sha512-pGxkuVEInwLHgkNxUc4sdg4g3py7zUeCQ9sMfwyHAT+Ezk8a4OaaVZ8lIY5+oNqA/BXXgLyXv0+5wHP68R79hg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-custom-media": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.2.tgz", + "integrity": "sha512-7yi25vDAoHAkbhAzX9dHx2yc6ntS4jQvejrNcC+csQJAXjj15e7VcWfMgLqBNAbOvqi5uIa9huOVwdHbf+sKqg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-custom-properties": { + "version": "12.1.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-12.1.11.tgz", + "integrity": "sha512-0IDJYhgU8xDv1KY6+VgUwuQkVtmYzRwu+dMjnmdMafXYv86SWqfxkc7qdDvWS38vsjaEtv8e0vGOUQrAiMBLpQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-6.0.3.tgz", + "integrity": "sha512-fgVkmyiWDwmD3JbpCmB45SvvlCD6z9CG6Ie6Iere22W5aHea6oWa7EM2bpnv2Fj3I94L3VbtvX9KqwSi5aFzSg==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.3" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.2.tgz", + "integrity": "sha512-GX+FuE/uBR6eskOK+4vkXgT6pDkexLokPaz/AbJna9s5Kzp/yl488pKPjhy0obB475ovfT1Wv8ho7U/cHNaRgQ==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-env-function": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-4.0.6.tgz", + "integrity": "sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-flexbugs-fixes": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz", + "integrity": "sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ==", + "peerDependencies": { + "postcss": "^8.1.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz", + "integrity": "sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz", + "integrity": "sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.9" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.5.tgz", + "integrity": "sha512-IuE6gKSdoUNcvkGIqdtjtcMtZIFyXZhmFd5RUlg97iVEvp1BZKV5ngsAjCjrVy+14uhGBQl9tzmi1Qwq4kqVOg==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-image-set-function": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-4.0.7.tgz", + "integrity": "sha512-9T2r9rsvYzm5ndsBE8WgtrMlIT7VbtTfE7b3BQnudUqnBcBo7L758oc+o+pdj/dUV0l5wjwSdjeOH2DZtfv8qw==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-initial": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", + "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-lab-function": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz", + "integrity": "sha512-xuXll4isR03CrQsmxyz92LJB2xX9n+pZJ5jE9JgcnmsCammLyKdlzrBin+25dy6wIjfhJpKBAN80gsTlCgRk2w==", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^1.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-loader": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", + "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", + "dependencies": { + "cosmiconfig": "^7.0.0", + "klona": "^2.0.5", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-media-minmax": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", + "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nesting": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-10.2.0.tgz", + "integrity": "sha512-EwMkYchxiDiKUhlJGzWsD9b2zvq/r2SSubcRrgP+jujMXFzqvANLt16lJANC+5uZ6hjI7lpRmI6O8JIl+8l1KA==", + "dependencies": { + "@csstools/selector-specificity": "^2.0.0", + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-normalize": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize/-/postcss-normalize-10.0.1.tgz", + "integrity": "sha512-+5w18/rDev5mqERcG3W5GZNMJa1eoYYNGo8gB7tEwaos0ajk3ZXAI4mHGcNT47NE+ZnZD1pEpUOFLvltIwmeJA==", + "dependencies": { + "@csstools/normalize.css": "*", + "postcss-browser-comments": "^4", + "sanitize.css": "*" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "browserslist": ">= 4", + "postcss": ">= 8" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.3.tgz", + "integrity": "sha512-An6Ba4pHBiDtyVpSLymUUERMo2cU7s+Obz6BTrS+gxkbnSBNKSuD0AVUc+CpBMrpVPKKfoVz0WQCX+Tnst0i4A==", + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.4.tgz", + "integrity": "sha512-otYl/ylHK8Y9bcBnPLo3foYFLL6a6Ak+3EQBPOTR7luMYCOsiVTUk1iLvNf6tVPNGXcoL9Hoz37kpfriRIFb4A==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-7.0.5.tgz", + "integrity": "sha512-wR8igaZROA6Z4pv0d+bvVrvGY4GVHihBCBQieXFY3kuSuMyOmEnnfFzHl/tQuqHZkfkIVBEbDvYcFfHmpSet9g==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-preset-env": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-7.8.3.tgz", + "integrity": "sha512-T1LgRm5uEVFSEF83vHZJV2z19lHg4yJuZ6gXZZkqVsqv63nlr6zabMH3l4Pc01FQCyfWVrh2GaUeCVy9Po+Aag==", + "dependencies": { + "@csstools/postcss-cascade-layers": "^1.1.1", + "@csstools/postcss-color-function": "^1.1.1", + "@csstools/postcss-font-format-keywords": "^1.0.1", + "@csstools/postcss-hwb-function": "^1.0.2", + "@csstools/postcss-ic-unit": "^1.0.1", + "@csstools/postcss-is-pseudo-class": "^2.0.7", + "@csstools/postcss-nested-calc": "^1.0.0", + "@csstools/postcss-normalize-display-values": "^1.0.1", + "@csstools/postcss-oklab-function": "^1.1.1", + "@csstools/postcss-progressive-custom-properties": "^1.3.0", + "@csstools/postcss-stepped-value-functions": "^1.0.1", + "@csstools/postcss-text-decoration-shorthand": "^1.0.0", + "@csstools/postcss-trigonometric-functions": "^1.0.2", + "@csstools/postcss-unset-value": "^1.0.2", + "autoprefixer": "^10.4.13", + "browserslist": "^4.21.4", + "css-blank-pseudo": "^3.0.3", + "css-has-pseudo": "^3.0.4", + "css-prefers-color-scheme": "^6.0.3", + "cssdb": "^7.1.0", + "postcss-attribute-case-insensitive": "^5.0.2", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^4.2.4", + "postcss-color-hex-alpha": "^8.0.4", + "postcss-color-rebeccapurple": "^7.1.1", + "postcss-custom-media": "^8.0.2", + "postcss-custom-properties": "^12.1.10", + "postcss-custom-selectors": "^6.0.3", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-double-position-gradients": "^3.1.2", + "postcss-env-function": "^4.0.6", + "postcss-focus-visible": "^6.0.4", + "postcss-focus-within": "^5.0.4", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^3.0.5", + "postcss-image-set-function": "^4.0.7", + "postcss-initial": "^4.0.1", + "postcss-lab-function": "^4.2.1", + "postcss-logical": "^5.0.4", + "postcss-media-minmax": "^5.0.0", + "postcss-nesting": "^10.2.0", + "postcss-opacity-percentage": "^1.1.2", + "postcss-overflow-shorthand": "^3.0.4", + "postcss-page-break": "^3.0.4", + "postcss-place": "^7.0.5", + "postcss-pseudo-class-any-link": "^7.1.6", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.6.tgz", + "integrity": "sha512-9sCtZkO6f/5ML9WcTLcIyV1yz9D1rf0tWc+ulKcvV30s0iZKS/ONyETvoWsr6vnrmW+X+KmuK3gV/w5EWnT37w==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-6.0.1.tgz", + "integrity": "sha512-1i9affjAe9xu/y9uqWH+tD4r6/hDaXJruk8xn2x1vzxC2U3J3LKO3zJW4CyxlNhA56pADJ/djpEwpH1RClI2rQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/postcss-svgo/node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/postcss-svgo/node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" + }, + "node_modules/postcss-svgo/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss-svgo/node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dependencies": { + "asap": "~2.0.6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-app-polyfill": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz", + "integrity": "sha512-sZ41cxiU5llIB003yxxQBYrARBqe0repqPTTYBTmMqTz9szeBbE37BehCE891NZsmdZqqP+xWKdT3eo3vOzN8w==", + "dependencies": { + "core-js": "^3.19.2", + "object-assign": "^4.1.1", + "promise": "^8.1.0", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.9", + "whatwg-fetch": "^3.6.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-app-polyfill/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/react-chatbotify": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/react-chatbotify/-/react-chatbotify-1.7.0.tgz", + "integrity": "sha512-wZCF1++4MP/2fnCxEDkSm2ij1aKlKlvbkF7ikhZYDrJgOLStiJCFVbD2GXgA5bnmgUEib18kfllWmNjR3ZDOMQ==" + }, + "node_modules/react-dev-utils": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", + "integrity": "sha512-84Ivxmr17KjUupyqzFode6xKhjwuEJDROWKJy/BthkL7Wn6NJ8h4WE6k/exAv6ImS+0oZLRRW5j/aINMHyeGeQ==", + "dependencies": { + "@babel/code-frame": "^7.16.0", + "address": "^1.1.2", + "browserslist": "^4.18.1", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "detect-port-alt": "^1.1.6", + "escape-string-regexp": "^4.0.0", + "filesize": "^8.0.6", + "find-up": "^5.0.0", + "fork-ts-checker-webpack-plugin": "^6.5.0", + "global-modules": "^2.0.0", + "globby": "^11.0.4", + "gzip-size": "^6.0.0", + "immer": "^9.0.7", + "is-root": "^2.1.0", + "loader-utils": "^3.2.0", + "open": "^8.4.0", + "pkg-up": "^3.1.0", + "prompts": "^2.4.2", + "react-error-overlay": "^6.0.11", + "recursive-readdir": "^2.2.2", + "shell-quote": "^1.7.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/react-dev-utils/node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-error-overlay": { + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", + "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, + "node_modules/react-refresh": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", + "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-scripts": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", + "integrity": "sha512-8VAmEm/ZAwQzJ+GOMLbBsTdDKOpuZh7RPs0UymvBR2vRk4iZWCskjbFnxqjrzoIvlNNRZ3QJFx6/qDSi6zSnaQ==", + "dependencies": { + "@babel/core": "^7.16.0", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.3", + "@svgr/webpack": "^5.5.0", + "babel-jest": "^27.4.2", + "babel-loader": "^8.2.3", + "babel-plugin-named-asset-import": "^0.3.8", + "babel-preset-react-app": "^10.0.1", + "bfj": "^7.0.2", + "browserslist": "^4.18.1", + "camelcase": "^6.2.1", + "case-sensitive-paths-webpack-plugin": "^2.4.0", + "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.2.0", + "dotenv": "^10.0.0", + "dotenv-expand": "^5.1.0", + "eslint": "^8.3.0", + "eslint-config-react-app": "^7.0.1", + "eslint-webpack-plugin": "^3.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^10.0.0", + "html-webpack-plugin": "^5.5.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^27.4.3", + "jest-resolve": "^27.4.2", + "jest-watch-typeahead": "^1.0.0", + "mini-css-extract-plugin": "^2.4.5", + "postcss": "^8.4.4", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-loader": "^6.2.1", + "postcss-normalize": "^10.0.1", + "postcss-preset-env": "^7.0.1", + "prompts": "^2.4.2", + "react-app-polyfill": "^3.0.0", + "react-dev-utils": "^12.0.1", + "react-refresh": "^0.11.0", + "resolve": "^1.20.0", + "resolve-url-loader": "^4.0.0", + "sass-loader": "^12.3.0", + "semver": "^7.3.5", + "source-map-loader": "^3.0.0", + "style-loader": "^3.3.1", + "tailwindcss": "^3.0.2", + "terser-webpack-plugin": "^5.2.5", + "webpack": "^5.64.4", + "webpack-dev-server": "^4.6.0", + "webpack-manifest-plugin": "^4.0.2", + "workbox-webpack-plugin": "^6.4.1" + }, + "bin": { + "react-scripts": "bin/react-scripts.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "react": ">= 16", + "typescript": "^3.2.1 || ^4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recursive-readdir": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", + "dependencies": { + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve-url-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-4.0.0.tgz", + "integrity": "sha512-05VEMczVREcbtT7Bz+C+96eUO5HDNvdthIiMB34t7FcF8ehcu4wC0sSgPUubs3XW2Q3CNLJk/BJrCU9wVRymiA==", + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^7.0.35", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=8.9" + }, + "peerDependencies": { + "rework": "1.0.1", + "rework-visit": "1.0.0" + }, + "peerDependenciesMeta": { + "rework": { + "optional": true + }, + "rework-visit": { + "optional": true + } + } + }, + "node_modules/resolve-url-loader/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/resolve-url-loader/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, + "node_modules/resolve-url-loader/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/resolve-url-loader/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", + "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", + "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-terser": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", + "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "jest-worker": "^26.2.1", + "serialize-javascript": "^4.0.0", + "terser": "^5.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sanitize.css": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-13.0.0.tgz", + "integrity": "sha512-ZRwKbh/eQ6w9vmTjkuG0Ioi3HBwPFce0O+v//ve+aOq1oeCy7jMV2qzzAlpsNuqpqCBjjriM1lbtZbF/Q8jVyA==" + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.2.tgz", + "integrity": "sha512-BokxPoLjyl3iOrgkWaakaxqnelAJSS+0V+De0kKIq6lyWrXuiPgYTGp6z3iHmqljKAaLXwZa+ctD8GccRJeVvg==", + "dependencies": { + "abab": "^2.0.5", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead" + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" + }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "dependencies": { + "escodegen": "^1.8.1" + } + }, + "node_modules/static-eval/node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/static-eval/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/static-eval/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/static-eval/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-eval/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", + "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==" + }, + "node_modules/svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "deprecated": "This SVGO version is no longer supported. Upgrade to v2.x.x.", + "dependencies": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/svgo/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/svgo/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "node_modules/svgo/node_modules/css-what": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.2.tgz", + "integrity": "sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/svgo/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/svgo/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/svgo/node_modules/domutils/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/svgo/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/svgo/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/svgo/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/svgo/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/svgo/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/tailwindcss": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", + "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.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "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-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dependencies": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terser": { + "version": "5.31.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.2.tgz", + "integrity": "sha512-LGyRZVFm/QElZHy/CPr/O4eNZOZIzsrQ92y4v9UJe/pFJjypje2yI3C2FmPtvUEnhadlSbmG2nXtdcjHOjCfxw==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/throat": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz", + "integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==" + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tryer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", + "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==" + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha512-vRCqFv6UhXpWxZPyGDh/F3ZpNv8/qo7w6iufLpQg9aKnQ71qM4B5KiI7Mia9COcjEhrO9LueHpMYjYzsWH3OIg==" + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", + "integrity": "sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-vitals": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-2.1.4.tgz", + "integrity": "sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==" + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-manifest-plugin": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz", + "integrity": "sha512-YXUAwxtfKIJIKkhg03MKuiFAD72PlrqCiwdwO4VEXdRO5V0ORCNwaOwAZawPZalCbmH9kBDmXnNeQOw+BIEiow==", + "dependencies": { + "tapable": "^2.0.0", + "webpack-sources": "^2.2.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "webpack": "^4.44.2 || ^5.47.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-manifest-plugin/node_modules/webpack-sources": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.1.tgz", + "integrity": "sha512-y9EI9AO42JjEcrTJFOYmVywVZdKVUfOvDUPsJea5GIr1JOEGFVqwlY2K098fFoIjOkDzHn2AjRvM8dsBZu+gCA==", + "dependencies": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/webpack/node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-background-sync": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", + "integrity": "sha512-jkf4ZdgOJxC9u2vztxLuPT/UjlH7m/nWRQ/MgGL0v8BJHoZdVGJd18Kck+a0e55wGXdqyHO+4IQTk0685g4MUw==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-6.6.0.tgz", + "integrity": "sha512-nm+v6QmrIFaB/yokJmQ/93qIJ7n72NICxIwQwe5xsZiV2aI93MGGyEyzOzDPVz5THEr5rC3FJSsO3346cId64Q==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-build": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-6.6.0.tgz", + "integrity": "sha512-Tjf+gBwOTuGyZwMz2Nk/B13Fuyeo0Q84W++bebbVsfr9iLkDSo6j6PST8tET9HYA58mlRXwlMGpyWO8ETJiXdQ==", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.11.1", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^11.2.1", + "@rollup/plugin-replace": "^2.4.1", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^7.1.6", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.43.1", + "rollup-plugin-terser": "^7.0.0", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "6.6.0", + "workbox-broadcast-update": "6.6.0", + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-google-analytics": "6.6.0", + "workbox-navigation-preload": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-range-requests": "6.6.0", + "workbox-recipes": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0", + "workbox-streams": "6.6.0", + "workbox-sw": "6.6.0", + "workbox-window": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-6.6.0.tgz", + "integrity": "sha512-JfhJUSQDwsF1Xv3EV1vWzSsCOZn4mQ38bWEBR3LdvOxSPgB65gAM6cS2CX8rkkKHRgiLrN7Wxoyu+TuH67kHrw==", + "deprecated": "workbox-background-sync@6.6.0", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-6.6.0.tgz", + "integrity": "sha512-GDtFRF7Yg3DD859PMbPAYPeJyg5gJYXuBQAC+wyrWuuXgpfoOrIQIvFRZnQ7+czTIQjIr1DhLEGFzZanAT/3bQ==" + }, + "node_modules/workbox-expiration": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-6.6.0.tgz", + "integrity": "sha512-baplYXcDHbe8vAo7GYvyAmlS4f6998Jff513L4XvlzAOxcl8F620O91guoJ5EOf5qeXG4cGdNZHkkVAPouFCpw==", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-6.6.0.tgz", + "integrity": "sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==", + "deprecated": "It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained", + "dependencies": { + "workbox-background-sync": "6.6.0", + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-6.6.0.tgz", + "integrity": "sha512-utNEWG+uOfXdaZmvhshrh7KzhDu/1iMHyQOV6Aqup8Mm78D286ugu5k9MFD9SzBT5TcwgwSORVvInaXWbvKz9Q==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-precaching": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-6.6.0.tgz", + "integrity": "sha512-eYu/7MqtRZN1IDttl/UQcSZFkHP7dnvr/X3Vn6Iw6OsPMruQHiVjjomDFCNtd8k2RdjLs0xiz9nq+t3YVBcWPw==", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-6.6.0.tgz", + "integrity": "sha512-V3aICz5fLGq5DpSYEU8LxeXvsT//mRWzKrfBOIxzIdQnV/Wj7R+LyJVTczi4CQ4NwKhAaBVaSujI1cEjXW+hTw==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-recipes": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-6.6.0.tgz", + "integrity": "sha512-TFi3kTgYw73t5tg73yPVqQC8QQjxJSeqjXRO4ouE/CeypmP2O/xqmB/ZFBBQazLTPxILUQ0b8aeh0IuxVn9a6A==", + "dependencies": { + "workbox-cacheable-response": "6.6.0", + "workbox-core": "6.6.0", + "workbox-expiration": "6.6.0", + "workbox-precaching": "6.6.0", + "workbox-routing": "6.6.0", + "workbox-strategies": "6.6.0" + } + }, + "node_modules/workbox-routing": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-6.6.0.tgz", + "integrity": "sha512-x8gdN7VDBiLC03izAZRfU+WKUXJnbqt6PG9Uh0XuPRzJPpZGLKce/FkOX95dWHRpOHWLEq8RXzjW0O+POSkKvw==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-strategies": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-6.6.0.tgz", + "integrity": "sha512-eC07XGuINAKUWDnZeIPdRdVja4JQtTuc35TZ8SwMb1ztjp7Ddq2CJ4yqLvWzFWGlYI7CG/YGqaETntTxBGdKgQ==", + "dependencies": { + "workbox-core": "6.6.0" + } + }, + "node_modules/workbox-streams": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-6.6.0.tgz", + "integrity": "sha512-rfMJLVvwuED09CnH1RnIep7L9+mj4ufkTyDPVaXPKlhi9+0czCu+SJggWCIFbPpJaAZmp2iyVGLqS3RUmY3fxg==", + "dependencies": { + "workbox-core": "6.6.0", + "workbox-routing": "6.6.0" + } + }, + "node_modules/workbox-sw": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-6.6.0.tgz", + "integrity": "sha512-R2IkwDokbtHUE4Kus8pKO5+VkPHD2oqTgl+XJwh4zbF1HyjAbgNmK/FneZHVU7p03XUt9ICfuGDYISWG9qV/CQ==" + }, + "node_modules/workbox-webpack-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-webpack-plugin/-/workbox-webpack-plugin-6.6.0.tgz", + "integrity": "sha512-xNZIZHalboZU66Wa7x1YkjIqEy1gTR+zPM+kjrYJzqN7iurYZBctBLISyScjhkJKYuRrZUP0iqViZTh8rS0+3A==", + "dependencies": { + "fast-json-stable-stringify": "^2.1.0", + "pretty-bytes": "^5.4.1", + "upath": "^1.2.0", + "webpack-sources": "^1.4.3", + "workbox-build": "6.6.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "webpack": "^4.4.0 || ^5.9.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-webpack-plugin/node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/workbox-window": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-6.6.0.tgz", + "integrity": "sha512-L4N9+vka17d16geaJXXRjENLFldvkWy7JyGxElRD0JvBxvFEd8LOhr+uXCcar/NzAmIBRv9EZ+M+Qr4mOoBITw==", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "6.6.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.1.tgz", + "integrity": "sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==", + "peerDependencies": { + "zod": "^3.23.3" + } + } + } +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package.json b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package.json new file mode 100644 index 0000000000000000000000000000000000000000..b5670de0fbe2f90ba2b36eaa993be8a06d91bbdd --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/package.json @@ -0,0 +1,55 @@ +{ + "name": "app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@langchain/community": "^0.2.18", + "@langchain/core": "^0.2.14", + "@langchain/openai": "^0.2.1", + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "next": "^14.2.5", + "react": "^18.3.1", + "react-chatbotify": "^1.7.0", + "react-dom": "^18.3.1", + "react-scripts": "5.0.1", + "socket.io": "^4.7.2", + "socket.io-client": "^4.7.2", + "web-vitals": "^2.1.4" + }, + "devDependencies": { + "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.0.3", + "postcss": "^8", + "tailwindcss": "^3.3.0" + }, + "overrides": { + "typescript": "^5.5.3", + "@langchain/core": "^0.2.14" + }, + "scripts": { + "dev": "next dev -p 8501", + "build": "next build", + "lint": "next lint" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/pages/api/socket.io/index.js b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/pages/api/socket.io/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ebb0fbc670b2ba100916ce97b3805b867862d835 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/app/pages/api/socket.io/index.js @@ -0,0 +1,113 @@ +import { Server } from 'socket.io'; +import { RunnableWithMessageHistory } from '@langchain/core/runnables'; +import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts'; +import { ChatMessageHistory } from 'langchain/stores/message/in_memory'; +import { ChatOpenAI } from '@langchain/openai'; + +const model_service = process.env.MODEL_ENDPOINT || + 'http://localhost:8001'; + +///////////////////////////////////// +// Function to check if/which LLM is available +async function checkingModelService() { + let server; + const startTime = new Date(); + while (true) { + let result = await fetch(`${model_service}/v1/models`); + if (result.status === 200) { + server = 'Llamacpp_Python'; + break; + }; + + await new Promise(x => setTimeout(x, 100)); + }; + + const endTime = new Date(); + return { details: `${server} Model Service Available\n` + + `${(endTime.getSeconds() - startTime.getSeconds()) } seconds`, + server: server + }; +}; + +///////////////////////////////////// +// Functions to interact with the LLM + +let chainWithHistory; +const sessions = {}; + +function createLLM(server) { + if (server === 'Llamacpp_Python') { + const llm = new ChatOpenAI( + { openAIApiKey: 'EMPTY' }, + { baseURL: `${model_service}/v1` } + ); + return llm; + } else { + throw new Error('Unknown llm'); + }; +}; + +function createChain(server) { + const prompt = ChatPromptTemplate.fromMessages([ + [ 'system', + 'You are a helpful chat agent. ' + + 'Answer any questions asked but if you are not certain of the answer say so. ' + + 'Answer only with plain answer do not include any annotations or qualifiers.' + ], + new MessagesPlaceholder('history'), + [ 'human', '{input}' ] + ]); + + const llm = createLLM(server); + const chain = prompt.pipe(llm); + + chainWithHistory = new RunnableWithMessageHistory({ + runnable: chain, + getMessageHistory: (sessionId) => { + if (sessions[sessionId] === undefined) { + sessions[sessionId] = new ChatMessageHistory(); + } + return sessions[sessionId]; + }, + inputMessagesKey: 'input', + historyMessagesKey: 'history', + }); +}; + +async function answerQuestion(question, sessionId) { + const result = await chainWithHistory.invoke( + { input: question }, + { configurable: { sessionId: sessionId }} + ); + return result.content; +}; + +///////////////////////////////////// +// socket.io handler that provides the service to which +// the front end can connect to in order to request +// answers to questions +const SocketHandler = async (req, res) => { + if (res.socket.server.io) { + } else { + console.log('Socket is initializing'); + const io = new Server(res.socket.server, { path: '/api/socket.io'}); + res.socket.server.io = io; + + io.on('connection', (socket) => { + socket.on('close', () => { + sessions[socket.id] = undefined; + }); + socket.on('question', async (question) => { + const answer = await answerQuestion(question, socket.id); + socket.emit('answer', answer); + }); + }); + + const result = await checkingModelService(); + console.log(result.details); + createChain(result.server); + }; + res.end(); +}; + +export default SocketHandler; diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..51b697f1f431f51eaaab1ee45bd8b173052b8b18 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile @@ -0,0 +1,50 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot-nodejs, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=chatbot-nodejs +ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG TARGETARCH + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup + +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..eb8b9c7dadd70282e669bd8ec1b18b0fcd773f37 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/Containerfile.nocache @@ -0,0 +1,26 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot-nodejs, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=chatbot-nodejs + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/README.md b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..34e1a0e3cdf4476c42b75f0b6f429210c05290c9 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample chat application. + +Details on the application can be found [in the chatbot-nodejs/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/chatbot-nodejs + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/llamacpp_python_cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The chatbot can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status chatbot-nodejs +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop chatbot-nodejs +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/chatbot-nodejs.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/README.md b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..da8b666dde87db3f4a253a0d3b7898557fc16925 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/README.md @@ -0,0 +1,9 @@ +### Run chatbot as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/chatbot-nodejs.yaml ../build/chatbot-nodejs.kube ../build/chatbot-nodejs.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start chatbot-nodejs +``` diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.kube b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.kube new file mode 100644 index 0000000000000000000000000000000000000000..a878e09289abfcb3f449b15ce5d5e20b7497514f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Kubernetes YAML file used to do chatbot inferencing +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=chatbot-nodejs.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.yaml b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e80b10d31c7c2a591b1dae9f901036d1be7fcd62 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot-nodejs/quadlet/chatbot-nodejs.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: chatbot-nodejs + name: chatbot-nodejs +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: chatbot-nodejs-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: chatbot-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/chatbot/Makefile b/chatbot/recipes/natural_language_processing/chatbot/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..94a51513cb6506dcd1eeeac466e2cb6b1d636438 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= chatbot +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/natural_language_processing/chatbot/README.md b/chatbot/recipes/natural_language_processing/chatbot/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a7b87c380d76f90b2c6b4d34200aad6a309ade0a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/README.md @@ -0,0 +1,180 @@ +# Chat Application + + This recipe helps developers start building their own custom LLM enabled chat applications. It consists of two main components: the Model Service and the AI Application. + + There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + + The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the chat application below. + +![](/assets/chatbot_ui.png) + + +## Try the Chat Application + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Chatbot` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/chatbot.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/chatbot.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `chatbot`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead. +Please refer to the section below for more details about [interacting with the chatbot application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop chatbot +podman pod rm chatbot +``` + +## Download a model + +If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well +performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/chatbot +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the chat application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled chatbot applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample chatbot workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the chatbot application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/chatbot-bootc:latest +``` + +Upon a reboot, you'll see that the chatbot service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status chatbot +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/chatbot/ai-lab.yaml b/chatbot/recipes/natural_language_processing/chatbot/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..eaf794b50605c93ca25e67d2ed8fe02ea987137b --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: ChatBot_Streamlit + description: Chat with a model service in a web frontend. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: streamlit-chat-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/chatbot:latest diff --git a/chatbot/recipes/natural_language_processing/chatbot/app/Containerfile b/chatbot/recipes/natural_language_processing/chatbot/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..c87ed477e47f364dcd77f66338b9b6550bdab99a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /chat +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /chat/requirements.txt +COPY chatbot_ui.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "chatbot_ui.py" ] diff --git a/chatbot/recipes/natural_language_processing/chatbot/app/chatbot_ui.py b/chatbot/recipes/natural_language_processing/chatbot/app/chatbot_ui.py new file mode 100644 index 0000000000000000000000000000000000000000..40f52b057644b2690294f3014687f7decd1d91f6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/app/chatbot_ui.py @@ -0,0 +1,108 @@ +from langchain_openai import ChatOpenAI +from langchain.chains import LLMChain +from langchain_community.callbacks import StreamlitCallbackHandler +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain.memory import ConversationBufferWindowMemory +import streamlit as st +import requests +import time +import json +import os + +model_service = os.getenv("MODEL_ENDPOINT", + "http://localhost:8001") +model_service = f"{model_service}/v1" +model_service_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +request_kwargs = {} +if model_service_bearer is not None: + request_kwargs = {"headers": {"Authorization": f"Bearer {model_service_bearer}"}} + +@st.cache_resource(show_spinner=False) +def checking_model_service(): + start = time.time() + print("Checking Model Service Availability...") + ready = False + while not ready: + try: + request_cpp = requests.get(f'{model_service}/models', **request_kwargs) + request_ollama = requests.get(f'{model_service[:-2]}api/tags', **request_kwargs) + if request_cpp.status_code == 200: + server = "Llamacpp_Python" + ready = True + elif request_ollama.status_code == 200: + server = "Ollama" + ready = True + except: + pass + time.sleep(1) + print(f"{server} Model Service Available") + print(f"{time.time()-start} seconds") + return server + +def get_models(): + try: + response = requests.get(f"{model_service[:-2]}api/tags", **request_kwargs) + return [i["name"].split(":")[0] for i in + json.loads(response.content)["models"]] + except: + return None + +with st.spinner("Checking Model Service Availability..."): + server = checking_model_service() + +def enableInput(): + st.session_state["input_disabled"] = False + +def disableInput(): + st.session_state["input_disabled"] = True + +st.title("💬 Chatbot") +if "messages" not in st.session_state: + st.session_state["messages"] = [{"role": "assistant", + "content": "How can I help you?"}] +if "input_disabled" not in st.session_state: + enableInput() + +for msg in st.session_state.messages: + st.chat_message(msg["role"]).write(msg["content"]) + +@st.cache_resource() +def memory(): + memory = ConversationBufferWindowMemory(return_messages=True,k=3) + return memory + +model_name = os.getenv("MODEL_NAME", "") + +if server == "Ollama": + models = get_models() + with st.sidebar: + model_name = st.radio(label="Select Model", + options=models) + +llm = ChatOpenAI(base_url=model_service, + api_key="sk-no-key-required" if model_service_bearer is None else model_service_bearer, + model=model_name, + streaming=True, + callbacks=[StreamlitCallbackHandler(st.empty(), + expand_new_thoughts=True, + collapse_completed_thoughts=True)]) + +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are world class technical advisor."), + MessagesPlaceholder(variable_name="history"), + ("user", "{input}") +]) + +chain = LLMChain(llm=llm, + prompt=prompt, + verbose=False, + memory=memory()) + +if prompt := st.chat_input(disabled=st.session_state["input_disabled"],on_submit=disableInput): + st.session_state.messages.append({"role": "user", "content": prompt}) + st.chat_message("user").markdown(prompt) + response = chain.invoke(prompt) + st.chat_message("assistant").markdown(response["text"]) + st.session_state.messages.append({"role": "assistant", "content": response["text"]}) + enableInput() + st.rerun() \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot/app/requirements.txt b/chatbot/recipes/natural_language_processing/chatbot/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..dba29a90151c92bea95cb719eae8215d443d3c9d --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/app/requirements.txt @@ -0,0 +1,4 @@ +langchain==0.2.3 +langchain-openai==0.1.7 +langchain-community==0.2.4 +streamlit==1.34.0 \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile b/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..aee92a9b823bcb58a55d790ca6af2ccf19a5d5c6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile @@ -0,0 +1,50 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=chatbot +ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG TARGETARCH + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup + +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..b3d50517061667dcd313f451de5a97ff0734474c --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/bootc/Containerfile.nocache @@ -0,0 +1,26 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=chatbot + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/chatbot/bootc/README.md b/chatbot/recipes/natural_language_processing/chatbot/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b8b22513892119214e50ca12ec15894c6bf61593 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample chat application. + +Details on the application can be found [in the chatbot/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/chatbot + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/llamacpp_python_cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The chatbot can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status chatbot +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop chatbot +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/chatbot.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/chatbot/provision/playbook.yml b/chatbot/recipes/natural_language_processing/chatbot/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..d08e54411a121732bc89191d4741c37a3b0b9b34 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/provision/playbook.yml @@ -0,0 +1,63 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: fedora + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 10 + timeout: 60 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required Packages + ansible.builtin.package: + name: "{{ item }}" + state: present + with_items: + - podman + - python3-libdnf5 + + - name: Models host directory + ansible.builtin.file: + path: locallm/models + state: directory + + - name: Download Model + ansible.builtin.get_url: + url: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_S.gguf + dest: locallm/models + + - name: Run Model + containers.podman.podman_container: + name: llamacpp_python + image: ghcr.io/containers/llamacpp_python:latest + state: started + interactive: true + tty: true + detach: true + ports: + - 8001:8001 + volume: + - ./locallm/models:/locallm/models:ro,Z + env: + MODEL_PATH: models/llama-2-7b-chat.Q5_K_S.gguf + HOST: 0.0.0.0 + PORT: 8001 + + - name: Run Application + containers.podman.podman_container: + name: chatbot + image: ghcr.io/containers/chatbot:latest + state: started + interactive: true + tty: true + ports: + - 8501:8501 + env: + MODEL_ENDPOINT: http://10.88.0.1:8001 diff --git a/chatbot/recipes/natural_language_processing/chatbot/provision/requirements.yml b/chatbot/recipes/natural_language_processing/chatbot/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/recipes/natural_language_processing/chatbot/quadlet/README.md b/chatbot/recipes/natural_language_processing/chatbot/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..26fffec64f19cefc9fe69822cb9500c1c79feda2 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/quadlet/README.md @@ -0,0 +1,9 @@ +### Run chatbot as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/chatbot.yaml ../build/chatbot.kube ../build/chatbot.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start chatbot +``` diff --git a/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.kube b/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.kube new file mode 100644 index 0000000000000000000000000000000000000000..45069505f40cc15f661868ffb816413db35c5b4c --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Kubernetes YAML file used to do chatbot inferencing +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=chatbot.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.yaml b/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9fd2e00c165a75428221c210a2348530d05e66e5 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/chatbot/quadlet/chatbot.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: chatbot + name: chatbot +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: chatbot-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: chatbot-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/codegen/Makefile b/chatbot/recipes/natural_language_processing/codegen/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b355dc4a5b6c923ba6b9c8e30c2614f951313d87 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/Makefile @@ -0,0 +1,11 @@ +SHELL := /bin/bash +APP ?= codegen +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests +MODEL_IMAGE := quay.io/ai-lab/mistral-7b-code-16k-qlora:latest +MODEL_NAME := mistral-7b-code-16k-qlora.Q4_K_M.gguf diff --git a/chatbot/recipes/natural_language_processing/codegen/README.md b/chatbot/recipes/natural_language_processing/codegen/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c483d326992540f0ddd3babdb8468b54067c048b --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/README.md @@ -0,0 +1,182 @@ +# Code Generation Application + + This recipe helps developers start building their own custom LLM enabled code generation applications. It consists of two main components: the Model Service and the AI Application. + + There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + + The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the code generation application below. + +![](/assets/codegen_ui.png) + + +## Try the Code Generation Application + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Code Generation` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/codegen.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/codegen.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `codegen`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. +Please refer to the section below for more details about [interacting with the codegen application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop codegen +podman pod rm codgen +``` + +## Download a model + +If you are just getting started, we recommend using [Mistral-7B-code-16k-qlora](https://huggingface.co/Nondzu/Mistral-7B-code-16k-qlora). This is a well +performant mid-sized model with an apache-2.0 license fine tuned for code generation. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of Mistral-7B-code-16k-qlora, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF. + +There are a number of options for quantization level, but we recommend `Q4_K_M`. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/TheBloke/Mistral-7B-Code-16K-qlora-GGUF/resolve/main/mistral-7b-code-16k-qlora.Q4_K_M.gguf +cd ../recipes/natural_language_processing/codgen +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/codegen from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/codegen from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the code generation application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled code generation applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample code generation workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the code generation application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/codegen-bootc:latest +``` + +Upon a reboot, you'll see that the codegen service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status codegen +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/codegen/ai-lab.yaml b/chatbot/recipes/natural_language_processing/codegen/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..841902277683ebe8260f78fc009d7635cae7ee04 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: codegen-demo + description: Generate code in countless programming languages. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: codegen-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/codegen:latest diff --git a/chatbot/recipes/natural_language_processing/codegen/app/Containerfile b/chatbot/recipes/natural_language_processing/codegen/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..f57c27f75cf8c9346e159d26bf56f2a5e2516e19 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /codegen +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /codegen/requirements.txt +COPY codegen-app.py . +EXPOSE 8501 +ENTRYPOINT ["streamlit", "run", "codegen-app.py"] diff --git a/chatbot/recipes/natural_language_processing/codegen/app/codegen-app.py b/chatbot/recipes/natural_language_processing/codegen/app/codegen-app.py new file mode 100644 index 0000000000000000000000000000000000000000..b821edf834773ede63568798809167efb715abe8 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/app/codegen-app.py @@ -0,0 +1,72 @@ +import os +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.runnables import RunnablePassthrough +from langchain_community.callbacks import StreamlitCallbackHandler + +import streamlit as st +import requests +import time + +model_service = os.getenv("MODEL_ENDPOINT", "http://localhost:8001") +model_service = f"{model_service}/v1" +model_service_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +request_kwargs = {} +if model_service_bearer is not None: + request_kwargs = {"headers": {"Authorization": f"Bearer {model_service_bearer}"}} + +@st.cache_resource(show_spinner=False) +def checking_model_service(): + start = time.time() + print("Checking Model Service Availability...") + ready = False + while not ready: + try: + request = requests.get(f'{model_service}/models', **request_kwargs) + if request.status_code == 200: + ready = True + except: + pass + time.sleep(1) + print("Model Service Available") + print(f"{time.time()-start} seconds") + +with st.spinner("Checking Model Service Availability..."): + checking_model_service() + +st.title("Code Generation App") + +if "messages" not in st.session_state: + st.session_state["messages"] = [{"role": "assistant", + "content": "How can I help you?"}] + +for msg in st.session_state.messages: + st.chat_message(msg["role"]).write(msg["content"]) + +model_name = os.getenv("MODEL_NAME", "") + +llm = ChatOpenAI(base_url=model_service, + model=model_name, + api_key="EMPTY" if model_service_bearer is None else model_service_bearer, + streaming=True) + +# Define the Langchain chain +prompt = ChatPromptTemplate.from_template("""You are an helpful code assistant that can help developer to code for a given {input}. + Generate the code block at first, and explain the code at the end. + If the {input} is not making sense, please ask for more clarification.""") +chain = ( + {"input": RunnablePassthrough()} + | prompt + | llm +) + +if prompt := st.chat_input(): + st.session_state.messages.append({"role": "user", "content": prompt}) + st.chat_message("user").markdown(prompt) + + st_callback = StreamlitCallbackHandler(st.container()) + response = chain.invoke(prompt, {"callbacks": [st_callback]}) + + st.chat_message("assistant").markdown(response.content) + st.session_state.messages.append({"role": "assistant", "content": response.content}) + st.rerun() diff --git a/chatbot/recipes/natural_language_processing/codegen/app/requirements.txt b/chatbot/recipes/natural_language_processing/codegen/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..3d2031ab204e155806af45cd1ee145cf8811f70b --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/app/requirements.txt @@ -0,0 +1,3 @@ +langchain==0.1.20 +langchain-openai==0.1.7 +streamlit==1.34.0 diff --git a/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile b/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..92b1fe05ec89f5c7209a1c26b730403d282ab313 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile @@ -0,0 +1,46 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/codegen, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=codegen +ARG MODEL_IMAGE=quay.io/ai-lab/mistral-7b-code-16k-qlora:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG TARGETARCH + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup + +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..96ea3f44ccb9cd9c41ffd2fa68f230190895dd04 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/bootc/Containerfile.nocache @@ -0,0 +1,22 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/codegen, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=codegen + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/codegen/bootc/README.md b/chatbot/recipes/natural_language_processing/codegen/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..da796d3aa1dda2c558b00bdf9d5bed260c0f8ed6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample code generation application. + +Details on the application can be found [in the codegen/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/codegen + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/lamacpp-python-cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The codegen can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status codegen +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop codegen +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/codegen.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/codegen/llms-vscode-integration.md b/chatbot/recipes/natural_language_processing/codegen/llms-vscode-integration.md new file mode 100644 index 0000000000000000000000000000000000000000..bd5562a53103724e79effed1512fa41457e4340c --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/llms-vscode-integration.md @@ -0,0 +1,60 @@ +# Integrating Language Models with Visual Studio Code (VS Code) + +In this guide, we'll walk through the process of integrating a language model with Visual Studio Code (VS Code) to enhance code generation tasks and developer productivity. + +## Step 1: Install "Continue" Extension + +![Install Continue Extension](../../../assets/install_continue_extension.png) + +To begin, install the "Continue" extension from the Visual Studio Code Marketplace. Open VS Code and navigate to the Extensions view by clicking on the square icon in the sidebar or pressing `Ctrl+Shift+X`. Search for the "Continue" extension and click on the "Install" button to install it. + +## Step 2: Ensure Model Service is Running + +Before configuring the "Continue" extension, ensure that the Model Service is up and running. Follow the instructions provided in the existing [README.md](README.md) document to build and deploy the Model Service. Note the port and endpoint details for the Model Service. + +## Step 3: Configure "Continue" config.json File + +Once the Model Service is operational, configure the "Continue" extension. Open VS Code and access the Command Palette by pressing `Ctrl+Shift+P` (Windows/Linux) or `Cmd+Shift+P` (Mac). Type "Continue: Configure" and select the option to open the configuration file. Edit the `config.json` file with the appropriate configuration. + +``` +{ + "title": "YourTitleHere", + "model": "YourModelName", + "completionOptions": {}, + "apiBase": "http://localhost:8001/v1/", + "provider": "openai" +} +``` + +In addition to its core functionalities, the "Continue" extension offers a tab auto complete feature in its pre-release version. This feature enhances the coding experience by providing auto-complete suggestions tailored to your coding context within VS Code. To leverage this functionality with the custom model, follow these steps to configure the `config.json` file: + +**Configure `tabAutoCompleteModel`:** Define the model settings within the `tabAutoCompleteModel` object in the `config.json` file. This includes specifying the title, provider name, model name, and API endpoint. We use the same API endpoint from Step 2. + +``` +{ + "tabAutocompleteModel": { + "title": "Tab Autocomplete Model", + "provider": "provider name", + "model": "model name", + "apiBase": "https://" + }, + ... +} +``` + +**Adjust Configuration Parameters:** Customize the configuration parameters according to your preferences. For example, you can set options such as, `useCopyBuffer`, `useSuffix`, `maxPromptTokens`, `debounceDelay`, `prefixPercentage`, and `multilineCompletion`. + +``` + "tabAutocompleteOptions": { + "useCopyBuffer": false, + "useSuffix": false, + "maxPromptTokens": 100, + "debounceDelay": 4000, + "prefixPercentage": 0.5, + "multilineCompletions": "never" + } +``` + +With these configurations in place, you'll be able to interact with the models for tab autocomplete effectively within VS Code. Make any necessary adjustments to the parameter values based on your system's capabilities. + +By integrating tab autocomplete into your coding workflow, you can streamline code completion tasks and enhance productivity while working within VS Code. diff --git a/chatbot/recipes/natural_language_processing/codegen/provision/playbook.yml b/chatbot/recipes/natural_language_processing/codegen/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..15e061e58ba84faa26cd6f3c0f78dea992ac32d7 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/provision/playbook.yml @@ -0,0 +1,60 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: fedora + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 10 + timeout: 60 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required Packages + ansible.builtin.package: + name: podman + state: present + + - name: Models host directory + ansible.builtin.file: + path: locallm/models + state: directory + + - name: Download Model + ansible.builtin.get_url: + url: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_S.gguf + dest: locallm/models + + - name: Run Model + containers.podman.podman_container: + name: llamacpp_python + image: ghcr.io/containers/llamacpp_python:latest + state: started + interactive: true + tty: true + detach: true + ports: + - 8001:8001 + volume: + - ./locallm/models:/locallm/models:ro,Z + env: + MODEL_PATH: models/llama-2-7b-chat.Q5_K_S.gguf + HOST: 0.0.0.0 + PORT: 8001 + + - name: Run Application + containers.podman.podman_container: + name: codegen + image: ghcr.io/containers/codegen:latest + state: started + interactive: true + tty: true + ports: + - 8501:8501 + env: + MODEL_SERVICE_ENDPOINT: http://10.88.0.1:8001/v1 diff --git a/chatbot/recipes/natural_language_processing/codegen/provision/requirements.yml b/chatbot/recipes/natural_language_processing/codegen/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/recipes/natural_language_processing/codegen/quadlet/README.md b/chatbot/recipes/natural_language_processing/codegen/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..53cfafe13f064c46c8951a7f5677b77b3f716d62 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/quadlet/README.md @@ -0,0 +1,9 @@ +### Run codegen as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/codegen.yaml ../build/codegen.kube /usr/share/containers/systemd/codegen.kube ../build/codegen.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start codegen +``` diff --git a/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.kube b/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.kube new file mode 100644 index 0000000000000000000000000000000000000000..3c64098e3fbb3d026342236d93a53c5dedc37fe0 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Python script to run against downloaded LLM +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=codegen.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.yaml b/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.yaml new file mode 100644 index 0000000000000000000000000000000000000000..0dfcda15ea951c893cbf346f51031ada5cbd4d31 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/codegen/quadlet/codegen.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: codegen + name: codegen +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: codegen-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: codegen-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/function_calling/Makefile b/chatbot/recipes/natural_language_processing/function_calling/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..323faf71cd2bab9fb6846a29510e6e326678ac42 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= function_calling +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/natural_language_processing/function_calling/README.md b/chatbot/recipes/natural_language_processing/function_calling/README.md new file mode 100644 index 0000000000000000000000000000000000000000..c992588ef73bb45bd3dcd25e34b37918067c88e1 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/README.md @@ -0,0 +1,180 @@ +# Function Calling Application + + This recipe helps developers start building their own custom function calling enabled chat applications. It consists of two main components: the Model Service and the AI Application. + + There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + + The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the chat application below. + +![](/assets/chatbot_ui.png) + + +## Try the Function Calling Application + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Function Calling` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/chatbot.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/chatbot.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `chatbot`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead. +Please refer to the section below for more details about [interacting with the chatbot application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop chatbot +podman pod rm chatbot +``` + +## Download a model + +If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well +performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/chatbot +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/chatbot from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the chat application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled chatbot applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample chatbot workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the chatbot application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/chatbot-bootc:latest +``` + +Upon a reboot, you'll see that the chatbot service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status chatbot +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/function_calling/ai-lab.yaml b/chatbot/recipes/natural_language_processing/function_calling/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c392c7290fa83ebf3624b4a31b1a93084a753365 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: Function_Calling_Streamlit + description: Function calling a remote service with a model service in a web frontend. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: streamlit-function-calling-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/function-calling:latest diff --git a/chatbot/recipes/natural_language_processing/function_calling/app/Containerfile b/chatbot/recipes/natural_language_processing/function_calling/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..f13fb3e5815dd1964c4b12feddd7daf61e8c582d --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /function-call +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /function-call/requirements.txt +COPY *.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "app.py" ] diff --git a/chatbot/recipes/natural_language_processing/function_calling/app/app.py b/chatbot/recipes/natural_language_processing/function_calling/app/app.py new file mode 100644 index 0000000000000000000000000000000000000000..17eda1c181ec90684986eacfa7632ba4f543d77f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/app/app.py @@ -0,0 +1,110 @@ +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.pydantic_v1 import BaseModel, Field +from langchain_core.output_parsers import PydanticToolsParser +import streamlit as st +import requests +import time +import json +import os + +model_service = os.getenv("MODEL_ENDPOINT", + "http://localhost:8001") +model_service = f"{model_service}/v1" + +@st.cache_resource(show_spinner=False) +def checking_model_service(): + start = time.time() + print("Checking Model Service Availability...") + ready = False + while not ready: + try: + request_cpp = requests.get(f'{model_service}/models') + request_ollama = requests.get(f'{model_service[:-2]}api/tags') + if request_cpp.status_code == 200: + server = "Llamacpp_Python" + ready = True + elif request_ollama.status_code == 200: + server = "Ollama" + ready = True + except: + pass + time.sleep(1) + print(f"{server} Model Service Available") + print(f"{time.time()-start} seconds") + return server + +def get_models(): + try: + response = requests.get(f"{model_service[:-2]}api/tags") + return [i["name"].split(":")[0] for i in + json.loads(response.content)["models"]] + except: + return None + +with st.spinner("Checking Model Service Availability..."): + server = checking_model_service() + +def enableInput(): + st.session_state["input_disabled"] = False + +def disableInput(): + st.session_state["input_disabled"] = True + +st.title("💬 Function calling") +if "input_disabled" not in st.session_state: + enableInput() + +model_name = os.getenv("MODEL_NAME", "") + +if server == "Ollama": + models = get_models() + with st.sidebar: + model_name = st.radio(label="Select Model", + options=models) + +class getWeather(BaseModel): + """Get the current weather in a given latitude and longitude.""" + + latitude: float = Field(description="The latitude of a place") + longitude: float = Field(description="The longitude of a place") + + #https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&hourly=temperature_2m + def retrieve(self): + return requests.get("https://api.open-meteo.com/v1/forecast", params={'latitude': self.latitude, 'longitude': self.longitude, 'hourly': 'temperature_2m'}).json(); + +llm = ChatOpenAI(base_url=model_service, + api_key="sk-no-key-required", + model=model_name, + streaming=False, verbose=False).bind_tools(tools=[getWeather], tool_choice='auto') + +SYSTEM_MESSAGE=""" +You are a helpful assistant. +You can call functions with appropriate input when necessary. +""" + + +prompt = ChatPromptTemplate.from_messages([ + ("system", SYSTEM_MESSAGE), + ("user", "What's the weather like in {input} ?") +]) + +chain = prompt | llm | PydanticToolsParser(tools=[getWeather]) + +st.markdown(""" +This demo application will ask the LLM for the weather in the city given in the input field and +specify a tool that can get weather information given a latitude and longitude. The weather information +retrieval is implemented using open-meteo.com. +""") +container = st.empty() + +if prompt := st.chat_input(placeholder="Enter the city name:", disabled=not input): + with container: + st.write("Calling LLM") + response = chain.invoke(prompt) + with container: + st.write("Retrieving weather information") + temperatures = list(map(lambda r: r.retrieve(), response)) + print(temperatures[0]) + with container: + st.line_chart(temperatures[0]['hourly'], x='time', y='temperature_2m') diff --git a/chatbot/recipes/natural_language_processing/function_calling/app/requirements.txt b/chatbot/recipes/natural_language_processing/function_calling/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..dba29a90151c92bea95cb719eae8215d443d3c9d --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/app/requirements.txt @@ -0,0 +1,4 @@ +langchain==0.2.3 +langchain-openai==0.1.7 +langchain-community==0.2.4 +streamlit==1.34.0 \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile b/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..b83fa917bfdd453c6014f3dba7fda951ff323322 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile @@ -0,0 +1,50 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=function_calling +ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG TARGETARCH + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup + +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..fd87474cae45b208fcb2f3c8df15154b77c38ccc --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/bootc/Containerfile.nocache @@ -0,0 +1,26 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/chatbot, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=function_calling + +# Include growfs service +COPY build/usr/lib /usr/lib +COPY --chmod=0755 build/usr/libexec/bootc-generic-growpart /usr/libexec/bootc-generic-growpart + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/function_calling/bootc/README.md b/chatbot/recipes/natural_language_processing/function_calling/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b11ed1ed52a82ae805059e77cf6cc00254b0549d --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample function calling application. + +Details on the application can be found [in the chatbot/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/function_calling + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/llamacpp_python_cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The chatbot can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status function_calling +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop function_calling +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/function_calling.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/function_calling/provision/playbook.yml b/chatbot/recipes/natural_language_processing/function_calling/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..0f8a428b6880986651c69021c63ac28a6f4389cf --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/provision/playbook.yml @@ -0,0 +1,63 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: fedora + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 10 + timeout: 60 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required Packages + ansible.builtin.package: + name: "{{ item }}" + state: present + with_items: + - podman + - python3-libdnf5 + + - name: Models host directory + ansible.builtin.file: + path: locallm/models + state: directory + + - name: Download Model + ansible.builtin.get_url: + url: https://huggingface.co/MaziyarPanahi/Mistral-7B-Instruct-v0.3-GGUF/resolve/main/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf + dest: locallm/models + + - name: Run Model + containers.podman.podman_container: + name: llamacpp_python + image: ghcr.io/containers/llamacpp_python:latest + state: started + interactive: true + tty: true + detach: true + ports: + - 8001:8001 + volume: + - ./locallm/models:/locallm/models:ro,Z + env: + MODEL_PATH: models/Mistral-7B-Instruct-v0.3.Q4_K_M.gguf + HOST: 0.0.0.0 + PORT: 8001 + + - name: Run Application + containers.podman.podman_container: + name: function_calling + image: ghcr.io/containers/function_calling:latest + state: started + interactive: true + tty: true + ports: + - 8501:8501 + env: + MODEL_ENDPOINT: http://10.88.0.1:8001 diff --git a/chatbot/recipes/natural_language_processing/function_calling/provision/requirements.yml b/chatbot/recipes/natural_language_processing/function_calling/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/recipes/natural_language_processing/function_calling/quadlet/README.md b/chatbot/recipes/natural_language_processing/function_calling/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1c361fd52a187d92c1d31d1781b055825a3aaa4e --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/quadlet/README.md @@ -0,0 +1,9 @@ +### Run function calling as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/function_calling.yaml ../build/function_calling.kube ../build/function_calling.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start function_calling +``` diff --git a/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.kube b/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.kube new file mode 100644 index 0000000000000000000000000000000000000000..aabe9cc5ac8eaa28e112387cbcc5752f6ab24a85 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Kubernetes YAML file used to do function calling inferencing +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=function_calling.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.yaml b/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.yaml new file mode 100644 index 0000000000000000000000000000000000000000..9051282aeb97fa560d8e184f5efcf6c7bf7371e0 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/function_calling/quadlet/function_calling.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: function_calling + name: function_calling +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: function_calling-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: function_calling-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/rag/Makefile b/chatbot/recipes/natural_language_processing/rag/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4564e24792584505b4be5c50f333826a02a8ad1f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/Makefile @@ -0,0 +1,67 @@ +SHELL := /bin/bash +APP ?= rag +PORT ?= 8501 +CHROMADB_PORT ?= 8000 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests + +.PHONY: run-chromadb +run: + podman run -it -p $(CHROMADB_PORT):$(CHROMADB_PORT) -e CHROMADB_ENDPOINT=http://10.88.0.1:8000/v1 ${CHROMADB_IMAGE} + +# rag requires custom bootc because it uses an extra build-arg for CHROMADB_IMAGE (other apps use ../../common/Makefile.common target) +.PHONY: bootc +bootc: quadlet + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--arch %) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(FROM:%=--from %) \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + --security-opt label=disable \ + --cap-add SYS_ADMIN \ + --build-arg MODEL_IMAGE=$(MODEL_IMAGE) \ + --build-arg APP_IMAGE=$(APP_IMAGE) \ + --build-arg CHROMADB_IMAGE=$(CHROMADB_IMAGE) \ + --build-arg SERVER_IMAGE=$(SERVER_IMAGE) \ + --build-arg "SSHPUBKEY=$(SSH_PUBKEY)" \ + -f bootc/$(CONTAINERFILE) \ + -t ${BOOTC_IMAGE} . + @echo "" + @echo "Successfully built bootc image '${BOOTC_IMAGE}'." + @echo "You may now convert the image into a disk image via bootc-image-builder" + @echo "or the Podman Desktop Bootc Extension. For more information, please refer to" + @echo " * https://github.com/osbuild/bootc-image-builder" + @echo " * https://github.com/containers/podman-desktop-extension-bootc" + +# rag requires custom quadlet target for CHROMADB_IMAGE substitution +# (other apps use ../../common/Makefile.common target) +.PHONY: quadlet +quadlet: + # Modify quadlet files to match the server, model and app image + mkdir -p build + sed -e "s|SERVER_IMAGE|${SERVER_IMAGE}|" \ + -e "s|APP_IMAGE|${APP_IMAGE}|g" \ + -e "s|MODEL_IMAGE|${MODEL_IMAGE}|g" \ + -e "s|CHROMADB_IMAGE|${CHROMADB_IMAGE}|g" \ + -e "s|APP|${APP}|g" \ + quadlet/${APP}.image \ + > build/${APP}.image + sed -e "s|SERVER_IMAGE|${SERVER_IMAGE}|" \ + -e "s|APP_IMAGE|${APP_IMAGE}|g" \ + -e "s|MODEL_IMAGE|${MODEL_IMAGE}|g" \ + -e "s|CHROMADB_IMAGE|${CHROMADB_IMAGE}|g" \ + quadlet/${APP}.yaml \ + > build/${APP}.yaml + cp quadlet/${APP}.kube build/${APP}.kube + +# rag requires custom bootc-run because it uses an extra port for chromadb +# (other apps use ../../common/Makefile.common target) +.PHONY: bootc-run +bootc-run: + podman run -d --rm --name $(APP)-bootc -p 8080:8501 -p 8090:8000 --privileged \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + $(BOOTC_IMAGE) /sbin/init diff --git a/chatbot/recipes/natural_language_processing/rag/README.md b/chatbot/recipes/natural_language_processing/rag/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3ed84e348c5e9632d49d475d1a5579ec9e19e2e7 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/README.md @@ -0,0 +1,235 @@ +# RAG (Retrieval Augmented Generation) Chat Application + +This demo provides a simple recipe to help developers start to build out their own custom RAG (Retrieval Augmented Generation) applications. It consists of three main components; the Model Service, the Vector Database and the AI Application. + +There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + +In order for the LLM to interact with our documents, we need them stored and available in such a manner that we can retrieve a small subset of them that are relevant to our query. To do this we employ a Vector Database alongside an embedding model. The embedding model converts our documents into numerical representations, vectors, such that similarity searches can be easily performed. The Vector Database stores these vectors for us and makes them available to the LLM. In this recipe we can use [chromaDB](https://docs.trychroma.com/) or [Milvus](https://milvus.io/) as our Vector Database. + +Our AI Application will connect to our Model Service via it's OpenAI compatible API. In this example we rely on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with our Model Service and we use [Streamlit](https://streamlit.io/) for our UI layer. Below please see an example of the RAG application. + +![](/assets/rag_ui.png) + + +## Try the RAG chat application + +_COMING SOON to AI LAB_ +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `RAG Chatbot` and follow the instructions to start the application. + +If you prefer building and running the application from terminal, please run the following commands from this directory. + +First, build application's meta data and run the generated Kubernetes YAML which will spin up a Pod along with a number of containers: +``` +make quadlet +podman kube play build/rag.yaml +``` + +The Pod is named `rag`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: +``` +podman pod list +podman ps +``` + +To stop and remove the Pod, run: +``` +podman pod stop rag +podman pod rm rag +``` + +Once the Pod is running, please refer to the section below to [interact with the RAG chatbot application](#interact-with-the-ai-application). + +# Build the Application + +In order to build this application we will need two models, a Vector Database, a Model Service and an AI Application. + +* [Download models](#download-models) +* [Deploy the Vector Database](#deploy-the-vector-database) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) + +### Download models + +If you are just getting started, we recommend using [Granite-7B-Lab](https://huggingface.co/instructlab/granite-7b-lab-GGUF). This is a well +performant mid-sized model with an apache-2.0 license that has been quanitzed and served into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/rag +``` + +_A full list of supported open models is forthcoming._ + +In addition to the LLM, RAG applications also require an embedding model to convert documents between natural language and vector representations. For this demo we will use [`BAAI/bge-base-en-v1.5`](https://huggingface.co/BAAI/bge-base-en-v1.5) it is a fairly standard model for this use case and has an MIT license. + +The code snippet below can be used to pull a copy of the `BAAI/bge-base-en-v1.5` embedding model and store it in your `models/` directory. + +```python +from huggingface_hub import snapshot_download +snapshot_download(repo_id="BAAI/bge-base-en-v1.5", + cache_dir="models/", + local_files_only=False) +``` + +### Deploy the Vector Database + +To deploy the Vector Database service locally, simply use the existing ChromaDB or Milvus image. The Vector Database is ephemeral and will need to be re-populated each time the container restarts. When implementing RAG in production, you will want a long running and backed up Vector Database. + + +#### ChromaDB +```bash +podman pull chromadb/chroma +``` +```bash +podman run --rm -it -p 8000:8000 chroma +``` +#### Milvus +```bash +podman pull milvusdb/milvus:master-20240426-bed6363f +``` +```bash +podman run -it \ + --name milvus-standalone \ + --security-opt seccomp:unconfined \ + -e ETCD_USE_EMBED=true \ + -e ETCD_CONFIG_PATH=/milvus/configs/embedEtcd.yaml \ + -e COMMON_STORAGETYPE=local \ + -v $(pwd)/volumes/milvus:/var/lib/milvus \ + -v $(pwd)/embedEtcd.yaml:/milvus/configs/embedEtcd.yaml \ + -p 19530:19530 \ + -p 9091:9091 \ + -p 2379:2379 \ + --health-cmd="curl -f http://localhost:9091/healthz" \ + --health-interval=30s \ + --health-start-period=90s \ + --health-timeout=20s \ + --health-retries=3 \ + milvusdb/milvus:master-20240426-bed6363f \ + milvus run standalone 1> /dev/null +``` +Note: For running the Milvus instance, make sure you have the `$(pwd)/volumes/milvus` directory and `$(pwd)/embedEtcd.yaml` file as shown in this repository. These are required by the database for its operations. + + +### Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the [the llamacpp_python model-service document](../model_servers/llamacpp_python/README.md). + +The Model Service can be built with the following code snippet: + +```bash +cd model_servers/llamacpp_python +podman build -t llamacppserver -f ./base/Containerfile . +``` + + +### Deploy the Model Service + +The complete instructions for building and deploying the Model Service can be found in the [the llamacpp_python model-service document](../model_servers/llamacpp_python/README.md). + +The local Model Service relies on a volume mount to the localhost to access the model files. You can start your local Model Service using the following Podman command: +``` +podman run --rm -it \ + -p 8001:8001 \ + -v Local/path/to/locallm/models:/locallm/models \ + -e MODEL_PATH=models/ \ + -e HOST=0.0.0.0 \ + -e PORT=8001 \ + llamacppserver +``` + +### Build the AI Application + +Now that the Model Service is running we want to build and deploy our AI Application. Use the provided Containerfile to build the AI Application image in the `rag-langchain/` directory. + +```bash +cd rag +make APP_IMAGE=rag build +``` + +### Deploy the AI Application + +Make sure the Model Service and the Vector Database are up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. The same goes for the Vector Database. Make sure the `VECTORDB_HOST` is correctly set to `10.88.0.1` for communication within the Podman virtual machine. + +There also needs to be a volume mount into the `models/` directory so that the application can access the embedding model as well as a volume mount into the `data/` directory where it can pull documents from to populate the Vector Database. + +The following Podman command can be used to run your AI Application: + +```bash +podman run --rm -it -p 8501:8501 \ +-e MODEL_ENDPOINT=http://10.88.0.1:8001 \ +-e VECTORDB_HOST=10.88.0.1 \ +-v Local/path/to/locallm/models/:/rag/models \ +rag +``` + +### Interact with the AI Application + +Everything should now be up an running with the rag application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled RAG applications. + +### Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample RAG chatbot workload as a service that starts when a system is booted, cd into this folder +and run: + + +``` +make BOOTC_IMAGE=quay.io/your/rag-bootc:latest bootc +``` + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +``` +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 BOOTC_IMAGE=quay.io/your/rag-bootc:latest bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the RAG chatbot application, it's as simple as ssh-ing into the bootc system and running: + +``` +bootc switch quay.io/your/rag-bootc:latest +``` + +Upon a reboot, you'll see that the RAG chatbot service is running on the system. + +Check on the service with + +``` +ssh user@bootc-system-ip +sudo systemctl status rag +``` + +#### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +##### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` + +### Makefile variables + +There are several [Makefile variables](../../common/README.md) defined within each `recipe` Makefile which can be +used to override defaults for a variety of make targets. diff --git a/chatbot/recipes/natural_language_processing/rag/ai-lab.yaml b/chatbot/recipes/natural_language_processing/rag/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..16b17f1c9fe2e61df750a9f716641f8cefebf112 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/ai-lab.yaml @@ -0,0 +1,37 @@ +version: v1.0 +application: + type: language + name: rag-demo + description: A RAG chat bot using local documents. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: chromadb-server + contextdir: ../../../vector_dbs/chromadb + containerfile: Containerfile + vectordb: true + arch: + - arm64 + - amd64 + ports: + - 8000 + image: quay.io/ai-lab/chromadb:latest + - name: rag-inference-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/rag:latest diff --git a/chatbot/recipes/natural_language_processing/rag/app/Containerfile b/chatbot/recipes/natural_language_processing/rag/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..593ca9faa22b798e943b9e94ac2d913d70852aec --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/app/Containerfile @@ -0,0 +1,24 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +### Update sqlite for chroma +USER root +RUN dnf remove sqlite3 -y +RUN wget https://www.sqlite.org/2023/sqlite-autoconf-3410200.tar.gz +RUN tar -xvzf sqlite-autoconf-3410200.tar.gz +WORKDIR sqlite-autoconf-3410200 +RUN ./configure +RUN make +RUN make install +RUN mv /usr/local/bin/sqlite3 /usr/bin/sqlite3 +ENV LD_LIBRARY_PATH="/usr/local/lib" +#### +WORKDIR /rag +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /rag/requirements.txt +COPY rag_app.py . +COPY manage_vectordb.py . +EXPOSE 8501 +ENV HF_HUB_CACHE=/rag/models/ +RUN mkdir -p /rag/models/ +RUN chgrp -R 0 /rag/models/ && chmod -R g=u /rag/models/ +ENTRYPOINT [ "streamlit", "run" ,"rag_app.py" ] diff --git a/chatbot/recipes/natural_language_processing/rag/app/manage_vectordb.py b/chatbot/recipes/natural_language_processing/rag/app/manage_vectordb.py new file mode 100644 index 0000000000000000000000000000000000000000..82566abdc68058dfdefc7214e75a8607572e967d --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/app/manage_vectordb.py @@ -0,0 +1,81 @@ +from langchain_community.vectorstores import Chroma +from chromadb import HttpClient +from chromadb.config import Settings +import chromadb.utils.embedding_functions as embedding_functions +from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings +from langchain_community.vectorstores import Milvus +from pymilvus import MilvusClient +from pymilvus import connections, utility + +class VectorDB: + def __init__(self, vector_vendor, host, port, collection_name, embedding_model): + self.vector_vendor = vector_vendor + self.host = host + self.port = port + self.collection_name = collection_name + self.embedding_model = embedding_model + + def connect(self): + # Connection logic + print(f"Connecting to {self.host}:{self.port}...") + if self.vector_vendor == "chromadb": + self.client = HttpClient(host=self.host, + port=self.port, + settings=Settings(allow_reset=True,)) + elif self.vector_vendor == "milvus": + self.client = MilvusClient(uri=f"http://{self.host}:{self.port}") + return self.client + + def populate_db(self, documents): + # Logic to populate the VectorDB with vectors + e = SentenceTransformerEmbeddings(model_name=self.embedding_model) + print(f"Populating VectorDB with vectors...") + if self.vector_vendor == "chromadb": + embedding_func = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=self.embedding_model) + collection = self.client.get_or_create_collection(self.collection_name, + embedding_function=embedding_func) + if collection.count() < 1: + db = Chroma.from_documents( + documents=documents, + embedding=e, + collection_name=self.collection_name, + client=self.client + ) + print("DB populated") + else: + db = Chroma(client=self.client, + collection_name=self.collection_name, + embedding_function=e, + ) + print("DB already populated") + + elif self.vector_vendor == "milvus": + connections.connect(host=self.host, port=self.port) + if not utility.has_collection(self.collection_name): + print("Populating VectorDB with vectors...") + db = Milvus.from_documents( + documents, + e, + collection_name=self.collection_name, + connection_args={"host": self.host, "port": self.port}, + ) + print("DB populated") + else: + print("DB already populated") + db = Milvus( + e, + collection_name=self.collection_name, + connection_args={"host": self.host, "port": self.port}, + ) + return db + + def clear_db(self): + print(f"Clearing VectorDB...") + try: + if self.vector_vendor == "chromadb": + self.client.delete_collection(self.collection_name) + elif self.vector_vendor == "milvus": + self.client.drop_collection(self.collection_name) + print("Cleared DB") + except: + print("Couldn't clear the collection possibly because it doesn't exist") diff --git a/chatbot/recipes/natural_language_processing/rag/app/rag_app.py b/chatbot/recipes/natural_language_processing/rag/app/rag_app.py new file mode 100644 index 0000000000000000000000000000000000000000..b3591fc1e95a50897de9c88bb93f16f9cc5de062 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/app/rag_app.py @@ -0,0 +1,104 @@ +from langchain_openai import ChatOpenAI +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.runnables import RunnablePassthrough +from langchain.text_splitter import CharacterTextSplitter +from langchain_community.callbacks import StreamlitCallbackHandler +from langchain_community.document_loaders import TextLoader +from langchain_community.document_loaders import PyPDFLoader +from manage_vectordb import VectorDB +import tempfile +import streamlit as st +import os + +model_service = os.getenv("MODEL_ENDPOINT","http://0.0.0.0:8001") +model_service = f"{model_service}/v1" +model_service_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +model_name = os.getenv("MODEL_NAME", "") +chunk_size = os.getenv("CHUNK_SIZE", 150) +embedding_model = os.getenv("EMBEDDING_MODEL","BAAI/bge-base-en-v1.5") +vdb_vendor = os.getenv("VECTORDB_VENDOR", "chromadb") +vdb_host = os.getenv("VECTORDB_HOST", "0.0.0.0") +vdb_port = os.getenv("VECTORDB_PORT", "8000") +vdb_name = os.getenv("VECTORDB_NAME", "test_collection") + +vdb = VectorDB(vdb_vendor, vdb_host, vdb_port, vdb_name, embedding_model) +vectorDB_client = vdb.connect() +def split_docs(raw_documents): + text_splitter = CharacterTextSplitter(separator = ".", + chunk_size=int(chunk_size), + chunk_overlap=0) + docs = text_splitter.split_documents(raw_documents) + return docs + + +def read_file(file): + file_type = file.type + if file_type == "application/pdf": + temp = tempfile.NamedTemporaryFile() + with open(temp.name, "wb") as f: + f.write(file.getvalue()) + loader = PyPDFLoader(temp.name) + + if file_type == "text/plain": + temp = tempfile.NamedTemporaryFile() + with open(temp.name, "wb") as f: + f.write(file.getvalue()) + loader = TextLoader(temp.name) + raw_documents = loader.load() + return raw_documents + +st.title("📚 RAG DEMO") +with st.sidebar: + file = st.file_uploader(label="📄 Upload Document", + type=[".txt",".pdf"], + on_change=vdb.clear_db + ) + +### populate the DB #### +if file != None: + text = read_file(file) + documents = split_docs(text) + db = vdb.populate_db(documents) + retriever = db.as_retriever(threshold=0.75) +else: + retriever = {} + print("Empty VectorDB") + + +######################## + +if "messages" not in st.session_state: + st.session_state["messages"] = [{"role": "assistant", + "content": "How can I help you?"}] + +for msg in st.session_state.messages: + st.chat_message(msg["role"]).write(msg["content"]) + + +llm = ChatOpenAI(base_url=model_service, + api_key="EMPTY" if model_service_bearer is None else model_service_bearer, + model=model_name, + streaming=True, + callbacks=[StreamlitCallbackHandler(st.container(), + collapse_completed_thoughts=True)]) + +prompt = ChatPromptTemplate.from_template("""Answer the question based only on the following context: +{context} + +Question: {input} +""" +) + +chain = ( + {"context": retriever, "input": RunnablePassthrough()} + | prompt + | llm +) + +if prompt := st.chat_input(): + st.session_state.messages.append({"role": "user", "content": prompt}) + st.chat_message("user").markdown(prompt) + response = chain.invoke(prompt) + st.chat_message("assistant").markdown(response.content) + st.session_state.messages.append({"role": "assistant", "content": response.content}) + st.rerun() diff --git a/chatbot/recipes/natural_language_processing/rag/app/requirements.txt b/chatbot/recipes/natural_language_processing/rag/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..e1a2865a3a015b89c7f99bdf0e9091b1c9ab6d11 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/app/requirements.txt @@ -0,0 +1,8 @@ +langchain-openai==0.1.7 +langchain==0.1.20 +chromadb==0.5.13 +sentence-transformers==2.7.0 +streamlit==1.34.0 +pypdf==4.2.0 +pymilvus==2.4.1 + diff --git a/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile b/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..021be3d2752f56c3bcc92138ce270d050bebb2b6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile @@ -0,0 +1,48 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/rag, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 + +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=rag +ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG CHROMADBImage=quay.io/ai-lab/chromadb +ARG TARGETARCH + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${CHROMADBImage} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..126286fd46376a4282c2fe0e4225168b2bec9471 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/bootc/Containerfile.nocache @@ -0,0 +1,22 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/rag, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=rag + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/rag/bootc/README.md b/chatbot/recipes/natural_language_processing/rag/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..2f4531a63e8a4ace2458f702b5f53bd26d2109d3 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample chat application. + +Details on the application can be found [in the rag/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/rag + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/ai-lab/lamacpp-python-cuda:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The rag can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [Podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status rag +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop rag +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/rag.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/rag/provision/playbook.yml b/chatbot/recipes/natural_language_processing/rag/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..2f12786194f1db7bcd60233a71d694ecd3832f6f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/provision/playbook.yml @@ -0,0 +1,72 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: fedora + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 10 + timeout: 60 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required Packages + ansible.builtin.package: + name: podman + state: present + + - name: Models host directory + ansible.builtin.file: + path: locallm/models + state: directory + + - name: Download Model + ansible.builtin.get_url: + url: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_S.gguf + dest: locallm/models + + - name: Run Model + containers.podman.podman_container: + name: llamacpp_python + image: ghcr.io/containers/llamacpp_python:latest + state: started + interactive: true + tty: true + detach: true + ports: + - 8001:8001 + volume: + - ./locallm/models:/locallm/models:ro,Z + env: + MODEL_PATH: models/llama-2-7b-chat.Q5_K_S.gguf + HOST: 0.0.0.0 + PORT: 8001 + + - name: Run Application + containers.podman.podman_container: + name: rag + image: ghcr.io/containers/rag:latest + state: started + interactive: true + tty: true + ports: + - 8501:8501 + env: + MODEL_SERVICE_ENDPOINT: http://10.88.0.1:8001/v1 + + - name: Run Vector Database + containers.podman.podman_container: + name: chromadb + image: ghcr.io/containers/chromadb:latest + state: started + interactive: true + tty: true + ports: + - 8000:8000 + env: + CHROMADB_ENDPOINT: http://0.0.0.0:8000/v1 diff --git a/chatbot/recipes/natural_language_processing/rag/provision/requirements.yml b/chatbot/recipes/natural_language_processing/rag/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/recipes/natural_language_processing/rag/quadlet/README.md b/chatbot/recipes/natural_language_processing/rag/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1d59de2b07e9bf7e3d641268a66899bef3b11949 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/quadlet/README.md @@ -0,0 +1,9 @@ +### Run rag as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/rag.yaml ../build/rag.kube ../build/rag.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start rag +``` diff --git a/chatbot/recipes/natural_language_processing/rag/quadlet/rag.kube b/chatbot/recipes/natural_language_processing/rag/quadlet/rag.kube new file mode 100644 index 0000000000000000000000000000000000000000..82dadf82fa363d71af701c5e3133970afbeaf426 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/quadlet/rag.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Kubernetes YAML file used to do rag inferencing +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=rag.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/rag/quadlet/rag.yaml b/chatbot/recipes/natural_language_processing/rag/quadlet/rag.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3e9ff41bfe17a5cbf90ea3a3e5e04d656d5b8abe --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/quadlet/rag.yaml @@ -0,0 +1,55 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: rag + name: rag +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: rag-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: CHROMADB_ENDPOINT + value: http://0.0.0.0:800O/v1 + image: CHROMADB_IMAGE + name: rag-chromadb + ports: + - containerPort: 8000 + hostPort: 8000 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: rag-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/rag/sample-data/fake_meeting.txt b/chatbot/recipes/natural_language_processing/rag/sample-data/fake_meeting.txt new file mode 100644 index 0000000000000000000000000000000000000000..c1a6463e04bc8dd295044e51a769bda9e3fe50e3 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/rag/sample-data/fake_meeting.txt @@ -0,0 +1,29 @@ +[The scene is set in a luxurious conference room with the three executives seated around a large oak table. The room is well-lit and the atmosphere is professional and cordial.] +Executive 1: "Good morning, everyone. Thank you for joining me today to discuss our exciting new AI business venture." +Executive 2: "Of course, John. I'm thrilled to be here. This is a game-changer for our college and I can't wait to see it come to fruition." +Executive 3: "Indeed. As you know, AI is becoming increasingly important in various industries, and we believe that our venture will provide significant benefits to both our students and the business world as a whole." +Executive 1: "That's right. Our AI platform will offer personalized learning experiences for our students, tailored to their individual needs and goals. And for the business world, it will provide cutting-edge insights and predictions based on vast amounts of data, giving them a competitive edge in today's fast-paced marketplace." +Executive 2: "I see. So how do you plan to monetize this platform?" +Executive 3: "That's a great question. We plan to offer subscription-based services to businesses, as well as generate revenue through partnerships and collaborations with industry leaders. Additionally, we will also explore opportunities for licensing our AI technology to other organizations." +Executive 1: "Excellent. And what about security and privacy concerns? How do you plan to address those?" +Executive 2: "Absolutely. We understand the importance of protecting sensitive data, and we will implement robust security measures to ensure that our platform is secure and compliant with all relevant regulations." +Executive 3: "Yes, and we will also have strict data privacy policies in place to safeguard the personal information of our students and clients. Transparency and trust are key components of any successful AI venture, and we take those seriously." +Executive 1: "I couldn't agree more. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +[The three executives nod in agreement and begin brainstorming strategies for promoting their AI platform.] +Executive 1: "Absolutely. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +Executive 2: "Agreed. We should start by reaching out to industry leaders and thought leaders in the field of AI and education. They will be key in helping us spread the word and build momentum for our platform." +Executive 3: "Excellent idea. And we should also consider partnering with some of the leading AI research institutions and universities. They will be able to provide valuable insights and expertise that will help us refine and improve our platform." +Executive 1: "That's a great point. Partnerships are key in any successful venture, and we want to make sure that we're working with the best of the best in this field." +Executive 2: "Definitely. And once we have a solid proof of concept, we can start reaching out to potential clients and showcasing the value of our platform. I think we'll find a lot of interest from companies looking for innovative ways to improve their operations and stay ahead of the competition." +Executive 3: "I agree. And as we continue to develop and refine our platform, we can also start exploring new markets and applications for AI in education. There are so many possibilities here, and I'm excited to see where this journey takes us." +Certainly! Here is a continuation of the dialogue: +Executive 1: "Absolutely. Now that we have a solid plan in place, let's start making some noise about this exciting new venture. I think it has the potential to revolutionize the way we approach education and business." +Executive 2: "Agreed. We should start by reaching out to industry leaders and thought leaders in the field of AI and education. They will be key in helping us spread the word and build momentum for our platform." +Executive 3: "Excellent idea. And we should also consider partnering with some of the leading AI research institutions and universities. They will be able to provide valuable insights and expertise that will help us refine and improve our platform." +Executive 1: "That's a great point. Partnerships are key in any successful venture, and we want to make sure that we're working with the best of the best in this field." +Executive 2: "Definitely. And once we have a solid proof of concept, we can start reaching out to potential clients and showcasing the value of our platform. I think we'll find a lot of interest from companies looking for innovative ways to improve their operations and stay ahead of the competition." +Executive 3: "I agree. And as we continue to develop and refine our platform, we can also start exploring new markets and applications for AI in education. There are so many possibilities here, and I'm excited to see where this journey takes us." +Executive 1: "Absolutely. And speaking of markets, let's not forget about the potential for international expansion. We could be looking at a global market opportunity here, and we don't want to miss out on that." +Executive 2: "Agreed. We should definitely consider how we can tailor our platform to meet the unique needs of different cultures and regions around the world." +Executive 3: "Excellent point. And as we continue to grow and expand, we'll need to make sure that we have the right infrastructure in place to support our global ambitions." +[The three executives nod in agreement and begin brainstorming strategies for promoting their AI platform on a global scale.] \ No newline at end of file diff --git a/chatbot/recipes/natural_language_processing/summarizer/Makefile b/chatbot/recipes/natural_language_processing/summarizer/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5794e127f2ae07c5c1e03376f7d961539460414a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash +APP ?= summarizer +PORT ?= 8501 + +include ../../common/Makefile.common + +RECIPE_BINARIES_PATH := $(shell realpath ../../common/bin) +RELATIVE_MODELS_PATH := ../../../models +RELATIVE_TESTS_PATH := ../tests diff --git a/chatbot/recipes/natural_language_processing/summarizer/README.md b/chatbot/recipes/natural_language_processing/summarizer/README.md new file mode 100644 index 0000000000000000000000000000000000000000..db96b4e4b16ed1bbb946ae83732323ef9a21a43e --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/README.md @@ -0,0 +1,180 @@ +# Text Summarizer Application + + This recipe helps developers start building their own custom LLM enabled summarizer applications. It consists of two main components: the Model Service and the AI Application. + + There are a few options today for local Model Serving, but this recipe will use [`llama-cpp-python`](https://github.com/abetlen/llama-cpp-python) and their OpenAI compatible Model Service. There is a Containerfile provided that can be used to build this Model Service within the repo, [`model_servers/llamacpp_python/base/Containerfile`](/model_servers/llamacpp_python/base/Containerfile). + + The AI Application will connect to the Model Service via its OpenAI compatible API. The recipe relies on [Langchain's](https://python.langchain.com/docs/get_started/introduction) python package to simplify communication with the Model Service and uses [Streamlit](https://streamlit.io/) for the UI layer. You can find an example of the summarizer application below. + +![](/assets/summarizer_ui.png) + + +## Try the Summarizer Application + +The [Podman Desktop](https://podman-desktop.io) [AI Lab Extension](https://github.com/containers/podman-desktop-extension-ai-lab) includes this recipe among others. To try it out, open `Recipes Catalog` -> `Summarizer` and follow the instructions to start the application. + +# Build the Application + +The rest of this document will explain how to build and run the application from the terminal, and will +go into greater detail on how each container in the Pod above is built, run, and +what purpose it serves in the overall application. All the recipes use a central [Makefile](../../common/Makefile.common) that includes variables populated with default values to simplify getting started. Please review the [Makefile docs](../../common/README.md), to learn about further customizing your application. + + +This application requires a model, a model service and an AI inferencing application. + +* [Quickstart](#quickstart) +* [Download a model](#download-a-model) +* [Build the Model Service](#build-the-model-service) +* [Deploy the Model Service](#deploy-the-model-service) +* [Build the AI Application](#build-the-ai-application) +* [Deploy the AI Application](#deploy-the-ai-application) +* [Interact with the AI Application](#interact-with-the-ai-application) +* [Embed the AI Application in a Bootable Container Image](#embed-the-ai-application-in-a-bootable-container-image) + + +## Quickstart +To run the application with pre-built images from `quay.io/ai-lab`, use `make quadlet`. This command +builds the application's metadata and generates Kubernetes YAML at `./build/summarizer.yaml` to spin up a Pod that can then be launched locally. +Try it with: + +``` +make quadlet +podman kube play build/summarizer.yaml +``` + +This will take a few minutes if the model and model-server container images need to be downloaded. +The Pod is named `summarizer`, so you may use [Podman](https://podman.io) to manage the Pod and its containers: + +``` +podman pod list +podman ps +``` + +Once the Pod and its containers are running, the application can be accessed at `http://localhost:8501`. However, if you started the app via the podman desktop UI, a random port will be assigned instead of `8501`. Please use the AI App Details `Open AI App` button to access it instead. +Please refer to the section below for more details about [interacting with the summarizer application](#interact-with-the-ai-application). + +To stop and remove the Pod, run: + +``` +podman pod stop summarizer +podman pod rm summarizer +``` + +## Download a model + +If you are just getting started, we recommend using [granite-7b-lab](https://huggingface.co/instructlab/granite-7b-lab). This is a well +performant mid-sized model with an apache-2.0 license. In order to use it with our Model Service we need it converted +and quantized into the [GGUF format](https://github.com/ggerganov/ggml/blob/master/docs/gguf.md). There are a number of +ways to get a GGUF version of granite-7b-lab, but the simplest is to download a pre-converted one from +[huggingface.co](https://huggingface.co) here: https://huggingface.co/instructlab/granite-7b-lab-GGUF/blob/main/granite-7b-lab-Q4_K_M.gguf. + +The recommended model can be downloaded using the code snippet below: + +```bash +cd ../../../models +curl -sLO https://huggingface.co/instructlab/granite-7b-lab-GGUF/resolve/main/granite-7b-lab-Q4_K_M.gguf +cd ../recipes/natural_language_processing/summarizer +``` + +_A full list of supported open models is forthcoming._ + + +## Build the Model Service + +The complete instructions for building and deploying the Model Service can be found in the +[llamacpp_python model-service document](../../../model_servers/llamacpp_python/README.md). + +The Model Service can be built from make commands from the [llamacpp_python directory](../../../model_servers/llamacpp_python/). + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make build +``` +Checkout the [Makefile](../../../model_servers/llamacpp_python/Makefile) to get more details on different options for how to build. + +## Deploy the Model Service + +The local Model Service relies on a volume mount to the localhost to access the model files. It also employs environment variables to dictate the model used and where its served. You can start your local Model Service using the following `make` command from `model_servers/llamacpp_python` set with reasonable defaults: + +```bash +# from path model_servers/llamacpp_python from repo containers/ai-lab-recipes +make run +``` + +## Build the AI Application + +The AI Application can be built from the make command: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/summarizer from repo containers/ai-lab-recipes) +make build +``` + +## Deploy the AI Application + +Make sure the Model Service is up and running before starting this container image. When starting the AI Application container image we need to direct it to the correct `MODEL_ENDPOINT`. This could be any appropriately hosted Model Service (running locally or in the cloud) using an OpenAI compatible API. In our case the Model Service is running inside the Podman machine so we need to provide it with the appropriate address `10.88.0.1`. To deploy the AI application use the following: + +```bash +# Run this from the current directory (path recipes/natural_language_processing/summarizer from repo containers/ai-lab-recipes) +make run +``` + +## Interact with the AI Application + +Everything should now be up an running with the summarizer application available at [`http://localhost:8501`](http://localhost:8501). By using this recipe and getting this starting point established, users should now have an easier time customizing and building their own LLM enabled summarizer applications. + +## Embed the AI Application in a Bootable Container Image + +To build a bootable container image that includes this sample summarizer workload as a service that starts when a system is booted, run: `make -f Makefile bootc`. You can optionally override the default image / tag you want to give the make command by specifying it as follows: `make -f Makefile BOOTC_IMAGE= bootc`. + +Substituting the bootc/Containerfile FROM command is simple using the Makefile FROM option. + +```bash +make FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 bootc +``` + +Selecting the ARCH for the bootc/Containerfile is simple using the Makefile ARCH= variable. + +``` +make ARCH=x86_64 bootc +``` + +The magic happens when you have a bootc enabled system running. If you do, and you'd like to update the operating system to the OS you just built +with the summarizer application, it's as simple as ssh-ing into the bootc system and running: + +```bash +bootc switch quay.io/ai-lab/summarizer-bootc:latest +``` + +Upon a reboot, you'll see that the summarizer service is running on the system. Check on the service with: + +```bash +ssh user@bootc-system-ip +sudo systemctl status summarizer +``` + +### What are bootable containers? + +What's a [bootable OCI container](https://containers.github.io/bootc/) and what's it got to do with AI? + +That's a good question! We think it's a good idea to embed AI workloads (or any workload!) into bootable images at _build time_ rather than +at _runtime_. This extends the benefits, such as portability and predictability, that containerizing applications provides to the operating system. +Bootable OCI images bake exactly what you need to run your workloads into the operating system at build time by using your favorite containerization +tools. Might I suggest [podman](https://podman.io/)? + +Once installed, a bootc enabled system can be updated by providing an updated bootable OCI image from any OCI +image registry with a single `bootc` command. This works especially well for fleets of devices that have fixed workloads - think +factories or appliances. Who doesn't want to add a little AI to their appliance, am I right? + +Bootable images lend toward immutable operating systems, and the more immutable an operating system is, the less that can go wrong at runtime! + +#### Creating bootable disk images + +You can convert a bootc image to a bootable disk image using the +[quay.io/centos-bootc/bootc-image-builder](https://github.com/osbuild/bootc-image-builder) container image. + +This container image allows you to build and deploy [multiple disk image types](../../common/README_bootc_image_builder.md) from bootc container images. + +Default image types can be set via the DISK_TYPE Makefile variable. + +`make bootc-image-builder DISK_TYPE=ami` diff --git a/chatbot/recipes/natural_language_processing/summarizer/ai-lab.yaml b/chatbot/recipes/natural_language_processing/summarizer/ai-lab.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f64b47910ca893d18a1b65a3f36116d9bc9e66e6 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/ai-lab.yaml @@ -0,0 +1,27 @@ +version: v1.0 +application: + type: language + name: Summarizer_App + description: Summarize text files in a web frontend. + containers: + - name: llamacpp-server + contextdir: ../../../model_servers/llamacpp_python + containerfile: ./base/Containerfile + model-service: true + backend: + - llama-cpp + arch: + - arm64 + - amd64 + ports: + - 8001 + image: quay.io/ai-lab/llamacpp_python:latest + - name: streamlit-summary-app + contextdir: app + containerfile: Containerfile + arch: + - arm64 + - amd64 + ports: + - 8501 + image: quay.io/ai-lab/summarizer:latest diff --git a/chatbot/recipes/natural_language_processing/summarizer/app/Containerfile b/chatbot/recipes/natural_language_processing/summarizer/app/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..9ce10d25f42aa4a84938262709b51c50747aca7f --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/app/Containerfile @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9/python-311:1-77.1726664316 +WORKDIR /summarizer +COPY requirements.txt . +RUN pip install --upgrade pip +RUN pip install --no-cache-dir --upgrade -r /summarizer/requirements.txt +COPY summarizer.py . +EXPOSE 8501 +ENTRYPOINT [ "streamlit", "run", "summarizer.py" ] diff --git a/chatbot/recipes/natural_language_processing/summarizer/app/requirements.txt b/chatbot/recipes/natural_language_processing/summarizer/app/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..45511c0a4c0c22ce8d4b704e21718a63ddf05439 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/app/requirements.txt @@ -0,0 +1,5 @@ +langchain==0.1.20 +langchain-openai==0.1.7 +streamlit==1.34.0 +PyMuPDF==1.24.11 +rouge_score==0.1.2 diff --git a/chatbot/recipes/natural_language_processing/summarizer/app/summarizer.py b/chatbot/recipes/natural_language_processing/summarizer/app/summarizer.py new file mode 100644 index 0000000000000000000000000000000000000000..7caf86dd4b44b3aa71b92d4a34af549f9cdcd272 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/app/summarizer.py @@ -0,0 +1,145 @@ + +from langchain_openai import ChatOpenAI +from langchain.prompts import PromptTemplate +from langchain_community.callbacks import StreamlitCallbackHandler +from langchain_community.document_loaders import PyMuPDFLoader +from langchain_text_splitters import RecursiveCharacterTextSplitter +from rouge_score import rouge_scorer +import streamlit as st +import tempfile +import requests +import json +import time +import os + +model_service = os.getenv("MODEL_ENDPOINT", + "http://localhost:8001") +model_service = f"{model_service}/v1" +model_service_bearer = os.getenv("MODEL_ENDPOINT_BEARER") +request_kwargs = {} +if model_service_bearer is not None: + request_kwargs["headers"] = {"Authorization": f"Bearer {model_service_bearer}"} + +@st.cache_resource(show_spinner=False) +def checking_model_service(): + start = time.time() + print("Checking Model Service Availability...") + ready = False + while not ready: + try: + request = requests.get(f'{model_service}/models', **request_kwargs) + if request.status_code == 200: + ready = True + except: + pass + time.sleep(1) + print("Model Service Available") + print(f"{time.time()-start} seconds") + +with st.spinner("Checking Model Service Availability..."): + checking_model_service() + +def split_append_chunk(chunk, list): + chunk_length = len(chunk) + chunk1 = " ".join(chunk.split()[:chunk_length]) + chunk2 = " ".join(chunk.split()[chunk_length:]) + list.extend([chunk1, chunk2]) + +def chunk_text(text): + chunks = [] + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=3048, + chunk_overlap=0, + length_function=len, + is_separator_regex=False + ) + + text_chunks = text_splitter.create_documents([text]) + for chunk in text_chunks: + chunk = chunk.page_content + chunk_kwargs = request_kwargs | {"json": {"input": chunk}} + count = requests.post(f"{model_service[:-2]}extras/tokenize/count", **chunk_kwargs).content + count = json.loads(count)["count"] + if count >= 2048: + split_append_chunk(chunk, chunks) + else: + chunks.append(chunk) + + return chunks + +def read_file(file): + file_type = file.type + + if file_type == "application/pdf": + temp = tempfile.NamedTemporaryFile() + with open(temp.name, "wb") as f: + f.write(file.getvalue()) + loader = PyMuPDFLoader(temp.name) + pages = loader.load() + text = "".join([p.page_content for p in pages]) + + if file_type in ["text/markdown", "text/plain", "application/octet-stream"]: + text = file.read().decode() + + return text + +def evaluate_summary(text, response): + metric = rouge_scorer.RougeScorer(rouge_types=["rouge2"]) + score = metric.score(target=text, + prediction=response) + return score + +st.title("🔎 Summarizer") +file = st.file_uploader("Upload file",type=[".txt",".pdf", ".md"]) + +llm = ChatOpenAI(base_url=model_service, + api_key="not required", + streaming=True, + temperature=0.0, + max_tokens=400, + ) + +### prompt example is from https://python.langchain.com/docs/use_cases/summarization +refine_template = PromptTemplate.from_template( + "Your job is to produce a final summary\n" + "We have provided an existing summary up to a certain point: {existing_answer}\n" + "We have the opportunity to refine the existing summary" + "(only if needed) with some more context below.\n" + "------------\n" + "{text}\n" + "------------\n" + "Given the new context, refine the original summary" + "If the context isn't useful, return the original summary." + "Only use bullet points." + "Dont ever go beyond 10 bullet points." +) + +if file != None: + + text = read_file(file) + chunks = chunk_text(text) + num_chunks = len(chunks) + st.write(f"Processing data in {num_chunks} chunks...") + progbar = st.progress(0.01, text="") + existing_answer = "" + + for i, chunk in enumerate(chunks): + progbar.progress((i+1)/(num_chunks), text="") + if i+1 < num_chunks: + response = llm.invoke(refine_template.format(text=chunk,existing_answer=existing_answer)) + existing_answer = response.content + else: + with st.spinner("Preparing Aggregated Summary"): + stream = llm.stream(refine_template.format(text=chunk,existing_answer=existing_answer)) + response = st.write_stream(stream) + + rouge = evaluate_summary(text,response) + with st.expander("See Evaluation Metrics!"): + st.markdown(f""" + #### Evaluation: + Rouge score: **{rouge["rouge2"].fmeasure:.3f}** + + _The rouge score values range from 0 to 1, where 1 is a perfect score. See more details about the rouge score + [here](https://huggingface.co/spaces/evaluate-metric/rouge)._ + """) + diff --git a/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile b/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..aeada03b4b04472ccc20e17e42f9c022652e4982 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile @@ -0,0 +1,46 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/summarizer, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=summarizer +ARG MODEL_IMAGE=quay.io/ai-lab/granite-7b-lab:latest +ARG APP_IMAGE=quay.io/ai-lab/${RECIPE}:latest +ARG SERVER_IMAGE=quay.io/ai-lab/llamacpp_python:latest +ARG TARGETARCH + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Because images are prepulled, no need for .image quadlet +# If commenting out the pulls below, uncomment this to track the images +# so the systemd service will wait for the images with the service startup +# COPY build/${RECIPE}.image /usr/share/containers/systemd + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +# Prepull the model, model_server & application images to populate the system. +# Comment the pull commands to keep bootc image smaller. +# The quadlet .image file added above pulls following images with service startup + +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${SERVER_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${APP_IMAGE} +RUN podman pull --arch=${TARGETARCH} --root /usr/lib/containers/storage ${MODEL_IMAGE} + +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile.nocache b/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile.nocache new file mode 100644 index 0000000000000000000000000000000000000000..727b9cee3fe99d7acbc2f921e5a398dd799634c9 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/bootc/Containerfile.nocache @@ -0,0 +1,22 @@ +# Example: an AI powered sample application is embedded as a systemd service +# via Podman quadlet files in /usr/share/containers/systemd +# +# from recipes/natural_language_processing/summarizer, run +# 'make bootc' + +FROM quay.io/centos-bootc/centos-bootc:stream9 +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys + +ARG RECIPE=summarizer + +# Add quadlet files to setup system to automatically run AI application on boot +COPY build/${RECIPE}.image build/${RECIPE}.kube build/${RECIPE}.yaml /usr/share/containers/systemd + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers diff --git a/chatbot/recipes/natural_language_processing/summarizer/bootc/README.md b/chatbot/recipes/natural_language_processing/summarizer/bootc/README.md new file mode 100644 index 0000000000000000000000000000000000000000..875602ca31db9b4af6b69f37d150a4d110c97600 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/bootc/README.md @@ -0,0 +1,94 @@ +## Embed workload (AI sample applications) in a bootable container image + +### Create a custom centos-bootc:stream9 image + +* [Containerfile](./Containerfile) - embeds an LLM-powered sample chat application. + +Details on the application can be found [in the chatbot/README.md](../README.md). By default, this Containerfile includes a model-server +that is meant to run with CPU - no additional GPU drivers or toolkits are embedded. You can substitute the llamacpp_python model-server image +for one that has GPU drivers and toolkits with additional build-args. The `FROM` must be replaced with a base image that has the necessary +kernel drivers and toolkits if building for GPU enabled systems. For an example of an NVIDIA/CUDA base image, +see [NVIDIA bootable image example](https://gitlab.com/bootc-org/examples/-/tree/main/nvidia?ref_type=heads) + +In order to pre-pull the workload images, you need to build from the same architecture you're building for. +If not pre-pulling the workload images, you can cross build (ie, build from a Mac for an X86_64 system). +To build the derived bootc image for x86_64 architecture, run the following: + +```bash +cd recipes/natural_language_processing/chatbot + +# for CPU powered sample LLM application +# to switch to an alternate platform like aarch64, pass --platform linux/arm64 +# the --cap-add SYS_ADMIN switch is needed when you are embedding Podman +# commands within the container build. If the registry you are pulling images +# from requires authentication, then you will need to volume mount the +# auth_json file with SELinux separation disabled. +podman login --auth-file auth.json quay.io/yourrepo +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --security-opt label=disable \ + -v ./auth.json:/run/containers/0/auth.json \ + --cap-add SYS_ADMIN \ + -t quay.io/yourrepo/youros:tag . + +# for GPU powered sample LLM application with llamacpp cuda model server +podman build --build-arg "sshpubkey=$(cat ~/.ssh/id_rsa.pub)" \ + --build-arg "model-server-image="quay.io/redhat-et/locallm-llamacpp-cuda-model-server:latest" \ + --from \ + --cap-add SYS_ADMIN \ + --platform linux/amd64 \ + -t quay.io/yourrepo/youros:tag . + +podman push quay.io/yourrepo/youros:tag +``` + +### Update a bootc-enabled system with the new derived image + +To build a disk image from an OCI bootable image, you can refer to [bootc-org/examples](https://gitlab.com/bootc-org/examples). +For this example, we will assume a bootc enabled system is already running. +If already running a bootc-enabled OS, `bootc switch` can be used to update the system to target a new bootable OCI image with embedded workloads. + +SSH into the bootc-enabled system and run: + +```bash +bootc switch quay.io/yourrepo/youros:tag +``` + +The necessary image layers will be downloaded from the OCI registry, and the system will prompt you to reboot into the new operating system. +From this point, with any subsequent modifications and pushes to the `quay.io/yourrepo/youreos:tag` OCI image, your OS can be updated with: + +```bash +bootc upgrade +``` + +### Accessing the embedded workloads + +The chatbot can be accessed by visiting port `8150` of the running bootc system. +They will be running as systemd services from Podman quadlet files placed at `/usr/share/containers/systemd/` on the bootc system. +For more information about running containerized applications as systemd services with Podman, refer to this +[Podman quadlet post](https://www.redhat.com/sysadmin/quadlet-podman) or, [podman documentation](https://podman.io/docs) + +To monitor the sample applications, SSH into the bootc system and run either: + +```bash +systemctl status chatbot +``` + +You can also view the pods and containers that are managed with systemd by running: + +``` +podman pod list +podman ps -a +``` + +To stop the sample applications, SSH into the bootc system and run: + +```bash +systemctl stop chatbot +``` + +To run the sample application _not_ as a systemd service, stop the services then +run the appropriate commands based on the application you have embedded. + +```bash +podman kube play /usr/share/containers/systemd/chatbot.yaml +``` diff --git a/chatbot/recipes/natural_language_processing/summarizer/provision/playbook.yml b/chatbot/recipes/natural_language_processing/summarizer/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..683d22e8c07610c7d6b54053e4616c8e6372ff4a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/provision/playbook.yml @@ -0,0 +1,60 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: fedora + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 10 + timeout: 60 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required Packages + ansible.builtin.package: + name: podman + state: present + + - name: Models host directory + ansible.builtin.file: + path: locallm/models + state: directory + + - name: Download Model + ansible.builtin.get_url: + url: https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_S.gguf + dest: locallm/models + + - name: Run Model + containers.podman.podman_container: + name: llamacpp_python + image: ghcr.io/containers/llamacpp_python:latest + state: started + interactive: true + tty: true + detach: true + ports: + - 8001:8001 + volume: + - ./locallm/models:/locallm/models:ro,Z + env: + MODEL_PATH: models/llama-2-7b-chat.Q5_K_S.gguf + HOST: 0.0.0.0 + PORT: 8001 + + - name: Run Application + containers.podman.podman_container: + name: summarizer + image: ghcr.io/containers/summarizer:latest + state: started + interactive: true + tty: true + ports: + - 8501:8501 + env: + MODEL_SERVICE_ENDPOINT: http://10.88.0.1:8001/v1 diff --git a/chatbot/recipes/natural_language_processing/summarizer/provision/requirements.yml b/chatbot/recipes/natural_language_processing/summarizer/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/recipes/natural_language_processing/summarizer/quadlet/README.md b/chatbot/recipes/natural_language_processing/summarizer/quadlet/README.md new file mode 100644 index 0000000000000000000000000000000000000000..78c57ad4203567bd60a4e667fccec5bfad727391 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/quadlet/README.md @@ -0,0 +1,9 @@ +### Run summarizer as a systemd service + +```bash +(cd ../;make quadlet) +sudo cp ../build/summarizer.yaml ../build/summarizer.kube ../build/summarizer.image /usr/share/containers/systemd/ +sudo /usr/libexec/podman/quadlet --dryrun #optional +sudo systemctl daemon-reload +sudo systemctl start summarizer +``` diff --git a/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.kube b/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.kube new file mode 100644 index 0000000000000000000000000000000000000000..a89ca07278ecc3484ee6697d6c7629b96699c384 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.kube @@ -0,0 +1,16 @@ +[Unit] +Description=Python script to run against downloaded LLM +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=%t/containers + +[Kube] +# Point to the yaml file in the same directory +Yaml=summarizer.yaml + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.yaml b/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.yaml new file mode 100644 index 0000000000000000000000000000000000000000..cc13ba3d824ea0cf7b2f44c4d3e5f49cbbb05927 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/summarizer/quadlet/summarizer.yaml @@ -0,0 +1,45 @@ +apiVersion: v1 +kind: Pod +metadata: + labels: + app: summarizer + name: summarizer +spec: + initContainers: + - name: model-file + image: MODEL_IMAGE + command: ['/usr/bin/install', "/model/model.file", "/shared/"] + volumeMounts: + - name: model-file + mountPath: /shared + containers: + - env: + - name: MODEL_ENDPOINT + value: http://0.0.0.0:8001 + image: APP_IMAGE + name: summarizer-inference + ports: + - containerPort: 8501 + hostPort: 8501 + securityContext: + runAsNonRoot: true + - env: + - name: HOST + value: 0.0.0.0 + - name: PORT + value: 8001 + - name: MODEL_PATH + value: /model/model.file + image: SERVER_IMAGE + name: summarizer-model-service + ports: + - containerPort: 8001 + hostPort: 8001 + securityContext: + runAsNonRoot: true + volumeMounts: + - name: model-file + mountPath: /model + volumes: + - name: model-file + emptyDir: {} diff --git a/chatbot/recipes/natural_language_processing/tests/conftest.py b/chatbot/recipes/natural_language_processing/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..7a2206fabb716996061f6288dfa3db482e92b2a1 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest +import os + + +@pytest.fixture +def chrome_options(chrome_options): + chrome_options.add_argument("--headless") + return chrome_options diff --git a/chatbot/recipes/natural_language_processing/tests/functional/__init__.py b/chatbot/recipes/natural_language_processing/tests/functional/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/recipes/natural_language_processing/tests/functional/conftest.py b/chatbot/recipes/natural_language_processing/tests/functional/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..35f5137b382b177678b67bfea19cdea4fd671217 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/functional/conftest.py @@ -0,0 +1,58 @@ +import pytest_container +import os +import logging + +REGISTRY=os.environ['REGISTRY'] +IMAGE_NAME=os.environ['IMAGE_NAME'] +MODEL_NAME=os.environ['MODEL_NAME'] + +logging.info(""" +Starting pytest with the following ENV vars: + REGISTRY: {REGISTRY} + IMAGE_NAME: {IMAGE_NAME} + MODEL_NAME: {MODEL_NAME} +For: + model_server: whispercpp +""".format(REGISTRY=REGISTRY, IMAGE_NAME=IMAGE_NAME, MODEL_NAME=MODEL_NAME)) + + +MS = pytest_container.Container( + url=f"containers-storage:{REGISTRY}/{IMAGE_NAME}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"/locallm/models/${MODEL_NAME}", + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": f"/locall/models/{MODEL_NAME}", + "HOST": "0.0.0.0", + "PORT": "8001" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=8001, + host_port=8001 + ) + ], + ) + +CB = pytest_container.Container( + url=f"containers-storage:{os.environ['REGISTRY']}/containers/{os.environ['IMAGE_NAME']}", + extra_environment_variables={ + "MODEL_ENDPOINT": "http://10.88.0.1:8001" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=8501, + host_port=8501 + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/chatbot/recipes/natural_language_processing/tests/functional/test_app.py b/chatbot/recipes/natural_language_processing/tests/functional/test_app.py new file mode 100644 index 0000000000000000000000000000000000000000..73cf26de7c733189e63e71f4c4d7cdc345fbc44a --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/functional/test_app.py @@ -0,0 +1,17 @@ +import pytest_container +from .conftest import CB +import tenacity + +CONTAINER_IMAGES = [CB] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip() + +def test_title(auto_container: pytest_container.container.ContainerData, selenium): + selenium.get(f"http://localhost:{auto_container.forwarded_ports[0].host_port}") + assert selenium.title == "Streamlit" + diff --git a/chatbot/recipes/natural_language_processing/tests/integration/__init__.py b/chatbot/recipes/natural_language_processing/tests/integration/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/chatbot/recipes/natural_language_processing/tests/integration/conftest.py b/chatbot/recipes/natural_language_processing/tests/integration/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..3a67a71cb6bce16d39980092ed5a325ccac02c99 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/integration/conftest.py @@ -0,0 +1,7 @@ +import os +import pytest + + +@pytest.fixture() +def url(): + return os.environ["URL"] diff --git a/chatbot/recipes/natural_language_processing/tests/integration/test_app.py b/chatbot/recipes/natural_language_processing/tests/integration/test_app.py new file mode 100644 index 0000000000000000000000000000000000000000..e2825e60cc0b1834d2ac9a5881468be5495b9d25 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/integration/test_app.py @@ -0,0 +1,3 @@ +def test_title(url,selenium): + selenium.get(f"http://{url}:8501") + assert selenium.title == "Streamlit" diff --git a/chatbot/recipes/natural_language_processing/tests/requirements.txt b/chatbot/recipes/natural_language_processing/tests/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..22fc97f27976ea5aebcc75d9586703a5b78df887 --- /dev/null +++ b/chatbot/recipes/natural_language_processing/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/chatbot/renovate.json b/chatbot/renovate.json new file mode 100644 index 0000000000000000000000000000000000000000..005469635d5b8558f83577892eeaab9d724a4074 --- /dev/null +++ b/chatbot/renovate.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>platform-engineering-org/.github" + ], + "packageRules": [ + { + "matchPackageNames": ["langchain"], + "allowedVersions": "<0.2.3" + }, + { + "matchPackageNames": ["langchain-openai"], + "allowedVersions": "<=0.1.7" + }, + { + "matchPackageNames": ["langchain-community"], + "allowedVersions": "<=0.2.4" + }, + { + "matchPackageNames": ["pymilvus"], + "allowedVersions": "<=2.4.1" + } + ] +} diff --git a/chatbot/requirements-test.txt b/chatbot/requirements-test.txt new file mode 100644 index 0000000000000000000000000000000000000000..66d120f46e78cf0544baf1ba75c2f2b8aba74804 --- /dev/null +++ b/chatbot/requirements-test.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.2 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.1 +pytest==8.2.2 +requests==2.31.0 +selenium==4.20.0 +tenacity==8.2.3 diff --git a/chatbot/training/Makefile b/chatbot/training/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a4411d21d0af5885e725d11e259abac7d873951b --- /dev/null +++ b/chatbot/training/Makefile @@ -0,0 +1,124 @@ +MAKEFLAGS += -j2 +default: help + +.PHONY: all +all: models + +help: + @echo "To build a bootable container image, run the target for your intended GPU arch" + @echo + @echo " - make amd" + @echo " - make intel" + @echo " - make nvidia" + @echo + @echo "To include models in the image, run the targets with a '-models' suffix." + @echo + @echo " - make amd-models" + @echo " - make intel-models" + @echo " - make nvidia-models" + @echo + @echo "Make prune. This command will remove all buildah containers if left behind from podman build and then prune all unused container images. Useful if you are running out of space." + @echo + @echo "Note: this will not remove the embedded instructlab images which are stored in the build directory." + @echo + @echo " - make prune" + @echo + @echo "To cleanup build state (downloaded models and embedded images)" + @echo + @echo " - make clean" + @echo + @echo "To create a disk image" + @echo + @echo " - make disk-amd" + @echo " - make disk-intel" + @echo " - make disk-nvidia" + +# +# Create instructlab AI container images +# +.PHONY: +instruct-amd: + $(MAKE) -C instructlab amd + +.PHONY: +instruct-nvidia: + $(MAKE) -C instructlab nvidia + +.PHONY: +instruct-intel: + $(MAKE) -C instructlab intel + +.PHONY: +instruct: instruct-amd instruct-nvidia instruct-intel + +.PHONY: deepspeed +deepspeed: + $(MAKE) -C deepspeed/ image + +.PHONY: vllm +vllm: + $(MAKE) -C vllm/ image + +# +# Create bootc container images prepared for AI +# +.PHONY: driver-tookit amd-bootc nvidia-bootc intel-bootc +driver-toolkit: + $(MAKE) -C common/ -f Makefile.common driver-toolkit +amd-bootc: + $(MAKE) -C amd-bootc/ bootc +intel-bootc: + $(MAKE) -C intel-bootc/ bootc +nvidia-bootc: + $(MAKE) -C nvidia-bootc/ driver-toolkit bootc + +# bootc container images with the instructlab dependency +.PHONY: amd nvidia intel +amd: instruct-amd amd-bootc +intel: instruct-intel intel-bootc +nvidia: instruct-nvidia nvidia-bootc + +# +# Create bootc container images prepared with AI models pre-loaded +# + +.PHONY: amd-bootc-models nvidia-bootc-models intel-bootc-models +amd-models: amd + $(MAKE) HARDWARE=amd -C model bootc-models +nvidia-models: nvidia + $(MAKE) HARDWARE=nvidia -C model bootc-models +intel-models: intel + $(MAKE) HARDWARE=intel -C model bootc-models + +.PHONY: bootc-models +bootc-models: + $(MAKE) FROM_BOOTC_IMAGE=$(BOOTC_CLOUD_IMAGE) TARGET_MODELS_IMAGE=$(BOOTC_CLOUD_MODELS_IMAGE) -C model bootc-models + +# +# We often see users running out of space. These commands are useful for freeing wasted space. +# Note be careful to not run this target if a podman build is in progress. +# +.PHONY: prune +prune: + buildah rm --all + podman image prune -f + +# Create disk images with bootc-image-builder +# +.PHONY: disk-amd +disk-amd: + $(MAKE) -C amd-bootc/ bootc-image-builder +.PHONY: disk-intel +disk-intel: + $(MAKE) -C intel-bootc/ bootc-image-builder +.PHONY: disk-nvidia +disk-nvidia: + $(MAKE) -C nvidia-bootc/ bootc-image-builder + +.PHONY: models +models: + $(MAKE) -C model + +.PHONY: clean +clean: + rm -rf build */build diff --git a/chatbot/training/README.md b/chatbot/training/README.md new file mode 100644 index 0000000000000000000000000000000000000000..826a9c627d939e10e14bf3877623ffe0947d384b --- /dev/null +++ b/chatbot/training/README.md @@ -0,0 +1,136 @@ +Linux Operating System Bootable containers enabled for AI Training +=== + +In order to run accelerated AI workloads, we've prepared [bootc](https://github.com/containers/bootc) container images for the major AI platforms. + +# Makefile targets + +| Target | Description | +|-----------------|---------------------------------------------------------------------| +| amd | Create bootable container for AMD platform | +| deepspeed | DeepSpeed container for optimization deep learning | +| disk-amd | Create disk image from bootable container for AMD platform | +| disk-intel | Create disk image from bootable container for Intel platform | +| disk-nvidia | Create disk image from bootable container for Nvidia platform | +| instruct-amd | Create instruct lab image for bootable container for AMD platform | +| instruct-intel | Create instruct lab image for bootable container for Intel platform | +| instruct-nvidia | Create instruct lab image for bootable container for Nvidia platform| +| intel | Create bootable container for Intel Habanalabs platform | +| nvidia | Create bootable container for NVidia platform | +| vllm | Containerized inference/serving engine for LLMs | + +# Makefile variables + +| Variable | Description | Default | +|---------------------------|-------------------------------------------------|---------------------------------------------| +| FROM | Overrides the base image for the Containerfiles | `quay.io/centos-bootc/centos-bootc:stream9` | +| REGISTRY | Container Registry for storing container images | `quay.io` | +| REGISTRY_ORG | Container Registry organization | `ai-lab` | +| IMAGE_NAME | Container image name | platform (i.e. `amd`) | +| IMAGE_TAG | Container image tag | `latest` | +| CONTAINER_TOOL | Container tool used for build | `podman` | +| CONTAINER_TOOL_EXTRA_ARGS | Container tool extra arguments | ` ` | +| VENDOR | Container image vendor label | ` ` | + + +Note: AI content is huge and requires a lot of disk space >200GB free to build. + +# How to build InstructLab containers + +In order to do AI Training you need to build instructlab container images. + +Simply execute `make instruct-`. For example: + +* make instruct-amd +* make instruct-intel +* make instruct-nvidia + +Once you have these container images built it is time to build vllm. + +# How to build the vllm inference engine + +* make vllm + +# How to build the deepspeed deepspeed container + +* make deepspeed + +# How to build bootc container images + +In order to build the images (by default based on CentOS Stream), a simple `make ` should be enough. For example to build the `nvidia`, `amd` and `intel` bootc containers, respectively: + +``` +make nvidia +make amd +make intel +``` + +## How to build bootc container images based on Red Hat Enterprise Linux + +In order to build the training images based on Red Hat Enterprise Linux bootc images, the appropriate base container image must be used in the `FROM` field and the build process must be run on an *entitled Red Hat 9.x Enterprise Linux* with a valid subscription. + +For example: + +``` +make nvidia FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 +make amd FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 +make intel FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 +``` + +Of course, the other Makefile variables are still available, so the following is a valid build command: + +``` +make nvidia REGISTRY=myregistry.com REGISTRY_ORG=ai-training IMAGE_NAME=nvidia IMAGE_TAG=v1 FROM=registry.redhat.io/rhel9/rhel-bootc:9.4 +``` + +# How to build disk images +bootc-image-builder produces disk images using a bootable container as input. Disk images can be used to directly provision a host +The process will write the disk image in -bootc/build + +IMPORTANT: `osbuild-selinux` package needs to be installed for bootc-image-builder to work in a SELinux enabled host + +To invoke bootc-image-builder, execute make disk- +``` +make disk-nvidia +``` +or +``` +make disk-nvidia DISK_TYPE=ami BOOTC_IMAGE=quay.io/ai-lab/nvidia-bootc-custom:latest +``` + +In addition to the variables common to all targets, a few extra can be defined to customize disk image creation + +| Variable | Description | Default | +|-----------------------|-----------------------------------|--------------------------------------------------| +| BOOTC_IMAGE | Image to use as input | `$REGISTRY/$REGISTRY_ORG/$IMAGE_NAME:$IMAGE_TAG` | +| DISK_TYPE | Type of image to build | `qcow2` | +| IMAGE_BUILDER_CONFIG | Path to a build-config file | `EMPTY` | + +Image builder config file is documented in [bootc-image-builder README](https://github.com/osbuild/bootc-image-builder?tab=readme-ov-file#-build-config) + +The following image disk types are currently available: +| Disk type | Target environment | +|-----------------------|---------------------------------------------------------------------------------------| +| `ami` | [Amazon Machine Image](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html) | +| `qcow2` **(default)** | [QEMU](https://www.qemu.org/) | +| `vmdk` | [VMDK](https://en.wikipedia.org/wiki/VMDK) usable in vSphere, among others | +| `anaconda-iso` | An unattended Anaconda installer that installs to the first disk found. | +| `raw` | Unformatted [raw disk](https://en.wikipedia.org/wiki/Rawdisk). | + +# Images customized for cloud providers + +For building images customized for each supported cloud provider, please read the [cloud providers section](cloud) + +# Troubleshooting + +Sometimes, interrupting the build process may lead to wanting a complete restart of the process. For those cases, we can instruct `podman` to start from scratch and discard the cached layers. This is possible by passing the `--no-cache` parameter to the build process by using the `CONTAINER_TOOL_EXTRA_ARGS` variable: + +``` +make CONTAINER_TOOL_EXTRA_ARGS="--no-cache" +``` + +The building of accelerated images requires a lot of temporary disk space. In case you need to specify a directory for temporary storage, this can be done with the `TMPDIR` environment variable: + +``` +make TMPDIR=/path/to/tmp +``` diff --git a/chatbot/training/amd-bootc/Containerfile b/chatbot/training/amd-bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..3a4f8bdf7aaf7f7342c228cf627dccccd5d726cf --- /dev/null +++ b/chatbot/training/amd-bootc/Containerfile @@ -0,0 +1,91 @@ +ARG INSTRUCTLAB_IMAGE="quay.io/ai-lab/instructlab-amd:latest" +ARG BASEIMAGE="quay.io/centos-bootc/centos-bootc:stream9" +ARG DRIVER_TOOLKIT_IMAGE="quay.io/ai-lab/nvidia-builder:latest" + +FROM ${DRIVER_TOOLKIT_IMAGE} AS builder + +COPY repos.d/amdgpu.repo /etc/yum.repos.d/amdgpu.repo +COPY repos.d/RPM-GPG-KEY-AMD-ROCM /etc/pki/rpm-gpg/RPM-GPG-KEY-AMD-ROCM + +USER root + +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-AMD-ROCM \ + && dnf install -y amdgpu-dkms \ + && dnf clean all + +FROM ${BASEIMAGE} + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +RUN --mount=type=bind,from=builder,source=/,destination=/tmp/builder,ro \ + export KERNEL_VERSION=$(rpm -q --qf '%{VERSION}-%{RELEASE}.%{ARCH}' kernel-core) \ + && rm -f /lib/modules/${KERNEL_VERSION}/kernel/drivers/gpu/drm/amd/amdgpu/amdgpu.ko.xz \ + && cp -r /tmp/builder/lib/modules/${KERNEL_VERSION}/extra /lib/modules/${KERNEL_VERSION}/extra \ + && cp -r /tmp/builder/lib/firmware/updates/amdgpu /lib/firmware/amdgpu \ + && depmod ${KERNEL_VERSION} + +ARG EXTRA_RPM_PACKAGES='' + +COPY repos.d/rocm.repo /etc/yum.repos.d/rocm.repo +COPY repos.d/RPM-GPG-KEY-AMD-ROCM /etc/pki/rpm-gpg/RPM-GPG-KEY-AMD-ROCM + +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-AMD-ROCM \ + && mv /etc/selinux /etc/selinux.tmp \ + && dnf install -y \ + amd-smi \ + cloud-init \ + git \ + git-lfs \ + pciutils \ + rsync \ + skopeo \ + tmux \ + ${EXTRA_RPM_PACKAGES} \ + && dnf clean all \ + && mv /etc/selinux.tmp /etc/selinux \ + && ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants + +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN if [ -n "${SSHPUBKEY}" ]; then \ + set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys; \ +fi + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +RUN grep -q /usr/lib/containers/storage /etc/containers/storage.conf || \ + sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +ARG INSTRUCTLAB_IMAGE="quay.io/ai-lab/instructlab-amd:latest" +ARG INSTRUCTLAB_IMAGE_PULL_SECRET="instructlab-amd-pull" + +COPY duplicated/ilab-wrapper/ilab /usr/bin/ilab +RUN chmod +x /usr/bin/ilab \ + && sed -i "s%__REPLACE_IMAGE_NAME__%${INSTRUCTLAB_IMAGE}%" /usr/bin/ilab + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +RUN --mount=type=secret,id=${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson \ + if [ -f "/run/.input/instructlab-amd/oci-layout" ]; then \ + IID=$(podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared pull oci:/run/.input/instructlab-amd) && \ + podman --root /usr/lib/containers/storage image tag ${IID} ${INSTRUCTLAB_IMAGE}; \ + elif [ -f "/run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson" ]; then \ + IID=$(sudo podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared pull --authfile /run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson ${INSTRUCTLAB_IMAGE}); \ + else \ + IID=$(sudo podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared pull ${INSTRUCTLAB_IMAGE}); \ + fi && \ + chmod -R a+X /usr/lib/containers + +COPY containers-storage.conf /etc/skel/.config/containers/storage.conf + +RUN podman system reset --force 2>/dev/null + +LABEL image_version_id="${IMAGE_VERSION_ID}" diff --git a/chatbot/training/amd-bootc/Makefile b/chatbot/training/amd-bootc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5154c582d0453f61a9040da2677dd952eaee0428 --- /dev/null +++ b/chatbot/training/amd-bootc/Makefile @@ -0,0 +1,25 @@ +HARDWARE ?= amd +IMAGE_NAME ?= $(HARDWARE)-bootc + +include ../common/Makefile.common + +default: bootc + +.PHONY: bootc +bootc: prepare-files + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(EXTRA_RPM_PACKAGES:%=--build-arg EXTRA_RPM_PACKAGES=%) \ + $(DRIVER_TOOLKIT_IMAGE:%=--build-arg DRIVER_TOOLKIT_IMAGE=%) \ + $(FROM:%=--build-arg BASEIMAGE=%) \ + $(INSTRUCTLAB_IMAGE:%=--build-arg INSTRUCTLAB_IMAGE=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + $(VENDOR:%=--build-arg VENDOR=%) \ + $(if $(SSH_PUBKEY),--build-arg SSHPUBKEY='$(SSH_PUBKEY)') \ + --cap-add SYS_ADMIN \ + --file Containerfile \ + --security-opt label=disable \ + --tag "${BOOTC_IMAGE}" \ + -v ${OUTDIR}:/run/.input:ro \ + ${CONTAINER_TOOL_EXTRA_ARGS} . diff --git a/chatbot/training/amd-bootc/containers-storage.conf b/chatbot/training/amd-bootc/containers-storage.conf new file mode 100644 index 0000000000000000000000000000000000000000..00a70bc70f1339bed6077d5793c9792916607997 --- /dev/null +++ b/chatbot/training/amd-bootc/containers-storage.conf @@ -0,0 +1,17 @@ +[storage] +driver = "overlay" + +[storage.options] +size = "" +remap-uids = "" +remap-gids = "" +ignore_chown_errors = "" +remap-user = "" +remap-group = "" +skip_mount_home = "" +mount_program = "/usr/bin/fuse-overlayfs" +mountopt = "" +additionalimagestores = [ "/usr/lib/containers/storage",] + +[storage.options.overlay] +force_mask = "shared" diff --git a/chatbot/training/amd-bootc/duplicated/ilab-wrapper/ilab b/chatbot/training/amd-bootc/duplicated/ilab-wrapper/ilab new file mode 100644 index 0000000000000000000000000000000000000000..09c900745715de1573c38e3a7d94acfbf5b7f44e --- /dev/null +++ b/chatbot/training/amd-bootc/duplicated/ilab-wrapper/ilab @@ -0,0 +1,100 @@ +#!/bin/bash + +echo-err() { echo "$@" >&2; } + +check_insights() { + if [[ -f /etc/insights-client/machine-id ]]; then + return + fi + if [[ -f /etc/ilab/insights-opt-out ]]; then + return + fi + local ID + eval "$(grep ^ID= /etc/os-release)" + if [[ "$ID" != "rhel" ]]; then + return + fi + cat << EOF +This host is not connected to Red Hat Insights. + +To connect this host to Red Hat Insights run the following command: +sudo rhc connect --organization --activation-key + +To generate an Activation Key: +https://console.redhat.com/insights/connector/activation-keys (this page will also display your Organization ID). + +For more information on Red Hat Insights, please visit: +https://docs.redhat.com/en/documentation/subscription_central/1-latest/html/getting_started_with_activation_keys_on_the_hybrid_cloud_console/assembly-creating-managing-activation-keys +EOF + exit 1 +} + +check_insights + +# Template values replaced by container build +IMAGE_NAME="__REPLACE_IMAGE_NAME__" + +ENTRYPOINT="ilab" +PARAMS=("$@") + +if [[ -n "$ILAB_HOME" ]]; then + HOME="$ILAB_HOME" +fi + +for dir in "$HOME/.cache" "$HOME/.config" "$HOME/.local"; do + mkdir -p "$dir" +done + +if [[ "$1" = "shell" ]]; then + ENTRYPOINT=bash + PARAMS=() +fi + +# If you need to mount additional volumes into the container, you can specify them +# using the ILAB_ADDITIONAL_MOUNTS environment variable. +# +# Example ILAB_ADDITIONAL_MOUNTS usage: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path /host/path2:/container/path2" +# +# If your path contains spaces, you can use quotes: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path '/host/path with spaces':/container/path" +ADDITIONAL_MOUNTS=() +if [ -n "${ILAB_ADDITIONAL_MOUNTS}" ]; then + # (eval is used here to allow the user to specify mounts that might have spaces in them) + eval "ADDITIONAL_MOUNTS=(${ILAB_ADDITIONAL_MOUNTS})" +fi +ADDITIONAL_MOUNT_OPTIONS=() +for PODMAN_MOUNT in "${ADDITIONAL_MOUNTS[@]}"; do + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$PODMAN_MOUNT") +done + +# Add pull-secret to additional mounts +# In case of normal user, /run/user is used (XDG_RUNTIME_DIR), if root, it will be /run/containers +for authfile in \ + "${XDG_RUNTIME_DIR}/containers/auth.json" \ + /run/user/${UID}/containers/auth.json \ + /run/containers/${UID}/auth.json +do + if [[ -f "$authfile" ]]; then + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$authfile:/run/containers/0/auth.json") + break + fi +done + +PODMAN_COMMAND=("podman" "run" "--rm" "-it" + "--device" "/dev/kfd" "--device" "/dev/dri" + "--security-opt" "label=disable" "--net" "host" + "--shm-size" "10G" + "--pids-limit" "-1" + "-v" "$HOME:$HOME" + "${ADDITIONAL_MOUNT_OPTIONS[@]}" + "--env" "VLLM_LOGGING_LEVEL" + "--env" "HOME" + "--env" "NCCL_DEBUG" + "--entrypoint" "$ENTRYPOINT" + "--env" "HF_TOKEN" + "${IMAGE_NAME}") + +exec "${PODMAN_COMMAND[@]}" "${PARAMS[@]}" diff --git a/chatbot/training/amd-bootc/repos.d/RPM-GPG-KEY-AMD-ROCM b/chatbot/training/amd-bootc/repos.d/RPM-GPG-KEY-AMD-ROCM new file mode 100644 index 0000000000000000000000000000000000000000..89815d0af2c1a23741aa6e3e258e9efe1a516150 --- /dev/null +++ b/chatbot/training/amd-bootc/repos.d/RPM-GPG-KEY-AMD-ROCM @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mQINBFefsSABEADmVqQyRi5bcUs/eG8mnKLdY+V+xuKuHLuujlXinSaMFRO640Md +C2HNYLSd58Z8cB1rKfiN639CZp+SkDWq60cFXDCcX9djT0JmBzsTD/gwoMr16tMY +O+Z2mje2pEYgDJdmYrephhXn29BfebW1IQKdA+4C7l675mJ/T8yVMUNXC0hqfGDA +h1MJUQy/lz1S2fGdjCKX0PiYOnCOyhNa7aTpw9PkZWgEa/s4BhplFZxvLohrCcf6 +ks0gUITHfeEhJvj2KurRfL68DgFifGnG+/fsMHgW1Xp19GsnIVaoh6cV7/iFHhrb +6YHI1fdOq/mwOfG8mJnXmDXC/o24Q7mRRwvoJcsT0j+thRirs8trV01mKY+7Hxd2 +CamWttibo062pjWN2aEUMPmEU2kmGOupsZtlpqn6SGCd2+6maOPMNEq/F0EWxhul +q6mgezVb8pvJ3bwvph2/lMSgfT9fHs6UIh4i/3rnA5/JaejFonlnS9xEuglKjklj +UoikSPBOwjvoPW2u99WCflURFSXVvuk7Ci+XkbVPIZyD6gFJjeY02Ic5MAv5tj/z +0fpgr/CfwEllms+z7qz768xRweA0kmPTTARdufVTna6EV3K3njxvCIIfnrp1cF6S +e3VrREd98gO0Rmzy74UFqkXl9Tb/+UILx1qVRmOBinwacKGqzo+k9jPUKQARAQAB +tChBTUQgTUxTRSBEZXZPcHMgPGRsLk1MU0UuRGV2T3BzQGFtZC5jb20+iQI+BBMB +AgAoAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAUCYfuRkwUJE8Hh5wAKCRCT +hrSKGmk8XI1AEACSJLVGHCLJOOKz9fbUR4KWl7Gpv0RWccwxhH01jNZTSXUCEnKA +2KYmaqFvrT5szxWILobmCNYtAlbdkpUfb0mMaF3UtTu+1UMOw2ExzxHw1FyA+z6d +vLqDKXLldsOFUfojDUhD5cK6uvONPc1orCf/4ve6wnRG838bAzb4VrFR64IxfPjx +NukH+jo2nEXNpnNv44DEiq65CcObaPuwAVBFnRYD/ByPO4ZArxFXqNzHRxpoZkKv +iwzhbPG4cirioqzRR9y2SsC+a2sO4a/jH0wOL2+n4L86xShYcuCBxXvS/AwrV/aO +JxKOfAUV4VQegAOQz64L+iz7PslNSTILJGdvGcC5Ckgpo6evdWBT7KdGXhzf4S1f +wZjYyP9sfQa7LxqyrkLHZqYt4If4Jmukx7cApBYp1nPnuCQrLU6D4Arq0ZVWQuNV +hbABLeqwdVQcX+vG/Kr/ZC+Vkv3Z8oElwVGAAQ6HNXr/u8ud2bu6iNJ5mcQbM1HD +KTNt5LUrk0p588a8dk0/TyC5xeKSv51iNL+aOVaTr0pRwgaHtEVar2i0FPC1mkr4 +1hhIDddx8WLoUt/52f1juyr/4CpL1M5f1cbMVjV6i0kqIEx/hxrryc+fZZQT5R4M +vysxcsh8ttgpABG5vzz2rLOCanmQ4eDdmlugzn/u0ngoDdnC0gEfnVVutLkCDQRX +n7EgARAAlsWVKSOQicuBxBlo3U5tre5whSyAOWHuy6/heGwCkGssTahbIL8pRwOL +5nKJCPCKKJ4YYoZ+Jzer9WTsDRZU/zpQXK9C5WdfF6DN/Fai3lqhgeDDVyF0hUDr +NQigm/w66JEYTGtMcC5PnYv7S6Zrn9WN4anv9n5thNwfsqxpbbg6sAQ2aLHLsW96 +myQE9v1s0YoSZYc7rFYBwszE+tFX0kLlyBYSRVns/USQifu66RObO706d8DHp6Ro +vO6WgsTu+0RR2FEUabBx1q6iKe1cqK0FYtWd8tXCpqQBm0zGC6UwTp4Z4GMCX2Pk +3xAMmrItW5kPKCANB+P/8ZoOoZLIX5Fr9axQ496lUh0ZDhOACewJfj9Szk9GN5rq ++2QKnRepatevGBVaN0lCAEwg2q9/9xmrT6CixFrbnw2T6mWHM3jQrvduqmC0c1Cd +uMZBGDKSpjouaN0UKtC+udwWiY7w452pcjCnUjzjk7tR1IarSCnLLYeb+MDCK83M +CFH60SmBfdqjRiTiLas34KSKNnmbfUfrTYswf0Oed/qXAUSlYOCmWl4sV8n+Ebpy +XfY80/fzu95RbpMEZMhUTRtvr64O5jaWM/lFnubnegGTW3Bk/fBR2VRsBx56ZHlc +JH23f6IREjQ1x4B2UsINYfyYpmzb+R4qpMzycBVHv9ipiYQsQ8sAEQEAAYkCJQQY +AQoADwIbDAUCYfuRtwUJE8HiEAAKCRCThrSKGmk8XMAcEACd0jYXjnu7qoEY4U9Q +47X2SeJmWsuTavCrU5AWxjYwWd0mtDqK8EynxDPq7UFs+8+OukqrE++p0bfBbDl9 +TwnwmSSdizAZriHMSgeg9GR5KVL4mreNhFQdk/6mTFdlRhi5s7ZuvPayLSMIAWaj +ET5gFMeO1B/ABSpaKEZwQjRcXrto/hCUJ++7qoosblhcgwX7fiqZZbMxcoCEQIQQ +7ZasLxpVtaeDVfetp2zO5F0/e3D/sNbvBrlDofSt6D5V2cmKjLqONFVc6JrzSNeK +k9Gn8UVzAKfRfLaQyDaoFV0MbBf3q111UQQPkvwZYp0lPT6t2/G8zoubwFhHsM31 +K5ZBbt0384hI9RJITo9/krXVXLYFeCLcoPKn/fGWgAwyYAYr6C7JcocxTNUyCd1I +AVg4SO/JuC3NWFQK5LhknN/gJkFlLZdB2cWqu9dDIkx1cHXThaM2n/7GSxv7fzrI +Br1jhZjUPWJ2iOd8iHgVEkIEvZql8z+huSxcNemodEN1emmUUoIyY3Fh0lJmozDt +ZPATk3iPpksOApsDVhWXP96RjTYEozYCxgTxCnk+kX/iJIlt53BPNWm9HMTcmtDI +v3s7OEcw0DN3U2VKcL9Q4Sg3uNfhwQsw/xBJaxAHQn5lN/8t0eLt+U653ooEEr0o +ta5TfPumStSQ1UjP8pPny4l+JQ== +=UOE+ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/chatbot/training/amd-bootc/repos.d/amdgpu.repo b/chatbot/training/amd-bootc/repos.d/amdgpu.repo new file mode 100644 index 0000000000000000000000000000000000000000..e24f978461574931cd55104439065f84a5684b55 --- /dev/null +++ b/chatbot/training/amd-bootc/repos.d/amdgpu.repo @@ -0,0 +1,7 @@ +[amdgpu] +name=amdgpu +baseurl=https://repo.radeon.com/amdgpu/6.2/el/9.4/main/x86_64/ +enabled=1 +priority=50 +gpgcheck=1 +gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key diff --git a/chatbot/training/amd-bootc/repos.d/rocm.repo b/chatbot/training/amd-bootc/repos.d/rocm.repo new file mode 100644 index 0000000000000000000000000000000000000000..57be60065db04c7de17fb47d564f6577746c3f5c --- /dev/null +++ b/chatbot/training/amd-bootc/repos.d/rocm.repo @@ -0,0 +1,7 @@ +[ROCm-6.2] +name=ROCm6.2 +baseurl=https://repo.radeon.com/rocm/el9/6.2/main +enabled=1 +priority=50 +gpgcheck=1 +gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key diff --git a/chatbot/training/cloud/Containerfile b/chatbot/training/cloud/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..ee5b6c92a16567f3250524136ac264d6c8e60919 --- /dev/null +++ b/chatbot/training/cloud/Containerfile @@ -0,0 +1,8 @@ +ARG BASEIMAGE=quay.io/ai-labs/bootc-nvidia:latest +FROM ${BASEIMAGE} + +ARG CLOUD + +COPY $CLOUD/cloud-setup.sh /tmp +RUN /tmp/cloud-setup.sh && rm -f /tmp/cloud-setup.sh +COPY $CLOUD/files/ / diff --git a/chatbot/training/cloud/Makefile b/chatbot/training/cloud/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4c4169371607c37d74495a30ee0044ed18a107f3 --- /dev/null +++ b/chatbot/training/cloud/Makefile @@ -0,0 +1,34 @@ +CLOUD ?= +VERSION ?= 1.1 +HARDWARE ?= nvidia +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +IMAGE_NAME ?= bootc-${HARDWARE}-rhel9-${CLOUD} +IMAGE_TAG ?= ${VERSION} +CONTAINER_TOOL ?= podman +CONTAINER_TOOL_EXTRA_ARGS ?= + +BOOTC_IMAGE_CLOUD ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}:${IMAGE_TAG} + +default: help + +-include $(CLOUD)/Makefile.env + +cloud-image: ## Create bootc image for a cloud, using stable RHEL AI as base + "${CONTAINER_TOOL}" build \ + $(BASEIMAGE:%=--build-arg BASEIMAGE=%) \ + $(CLOUD:%=--build-arg CLOUD=%) \ + ${CONTAINER_TOOL_EXTRA_ARGS} \ + --tag ${BOOTC_IMAGE_CLOUD} \ + --file Containerfile \ + . + +cloud-disk: ## Create disk image for a cloud, using the image built with cloud-image target + make -f ../common/Makefile.common bootc-image-builder \ + BOOTC_IMAGE=${BOOTC_IMAGE_CLOUD} \ + DISK_TYPE=${DISK_TYPE} \ + IMAGE_BUILDER_CONFIG=$(abspath $(CLOUD))/config.toml + +help: ## Shows this message. + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(shell echo "$(MAKEFILE_LIST) " | tac -s' ') | perl -pe 's/^.*Makefile.*?://g' | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + diff --git a/chatbot/training/cloud/README.md b/chatbot/training/cloud/README.md new file mode 100644 index 0000000000000000000000000000000000000000..926a140ca584bb4f6bdf229f2d8d665d672fbffd --- /dev/null +++ b/chatbot/training/cloud/README.md @@ -0,0 +1,47 @@ +Customizing RHEL AI for the different cloud providers +=== + +In order to create images for the different cloud providers, we need to add some extra packages and configuration, and create special disk images + +Please refer to the official RHEL AI documentation on how to create machine images for different clouds. + +# Makefile targets + +| Target | Description | +|-----------------|-----------------------------------------------------------------------| +| cloud-image | Create bootc image for a cloud, using stable RHEL AI as base | +| cloud-disk | Create disk image for a cloud, using the image built with cloud-image | + +# Makefile variables + +| Variable | Description | Default | +|---------------------------|--------------------------------------------------------|--------------------------------------------------------------| +| CLOUD | Sets the name of the cloud: aws, azure, gcp, ibm | ` ` | +| HARDWARE | Hardware accelerator RHEL AI source image | `nvidia` | +| VERSION | RHEL AI version | `1.1` | +| REGISTRY | Container Registry for storing container images | `quay.io` | +| REGISTRY_ORG | Container Registry organization | `ai-lab` | +| IMAGE_NAME | Container image name | `bootc-${HARDWARE}-rhel9-${CLOUD}` | +| IMAGE_TAG | Container image tag | `${CLOUD}-latest` | +| CONTAINER_TOOL | Container tool used for build | `podman` | +| CONTAINER_TOOL_EXTRA_ARGS | Container tool extra arguments | ` ` | +| BASEIMAGE | Source RHEL AI image | `registry.stage.redhat.io/rhelai1/bootc-nvidia-rhel9:latest` | +| BOOTC_IMAGE_CLOUD | Override cloud image name | `${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}:${IMAGE_TAG}` | + + +# Example on how to build your own AI Bootc disk image + +Simply execute `make cloud-image CLOUD= BASEIMAGE=`. For example: + +* make cloud-image CLOUD=ibm BASEIMAGE=quay.io/ai-lab/nvidia-bootc:1.1 +* make cloud-image CLOUD=gcp BASEIMAGE=quay.io/ai-lab/nvidia-bootc:1.1 + +Once you have the bootc image, you can use it to create a disk image. +Simply execute `make cloud-disk CLOUD=`. For example: + +* make cloud-disk CLOUD=ibm +* make cloud-disk CLOUD=gcp + + +This will produce an image in the `build/output` directory. +Then, you can follow RHEL AI documentation on how to create a machine image in your cloud provider. diff --git a/chatbot/training/cloud/aws/Makefile.env b/chatbot/training/cloud/aws/Makefile.env new file mode 100644 index 0000000000000000000000000000000000000000..2a20bb8dcf7f2b6332bc75aa11ddf4bc1c8867d3 --- /dev/null +++ b/chatbot/training/cloud/aws/Makefile.env @@ -0,0 +1 @@ +DISK_TYPE=ami diff --git a/chatbot/training/cloud/aws/README.md b/chatbot/training/cloud/aws/README.md new file mode 100644 index 0000000000000000000000000000000000000000..66630a3c6bac972fd29f70cc61f654bc7b110faa --- /dev/null +++ b/chatbot/training/cloud/aws/README.md @@ -0,0 +1,34 @@ +# Amazon Web Services modifications for RHEL AI +Trying to mimic as much as possible the [changes on RHEL for AWS](https://github.com/osbuild/images/blob/main/pkg/distro/rhel/rhel9/ami.go) + +## Changes + +- Extra kernel parameters + +``` +console=ttyS0,115200n8 net.ifnames=0 nvme_core.io_timeout=4294967295 +``` + +- Timezone: UTC +- Chrony configuration: + - Change server + - LeapsecTz +- Locale: en_US.UTF-8 +- Keymap: us +- X11 layout: us + +- Getty configuration + - NautoVTs false + +- Cloud init default user: `ec2-user` + +- Packages + - @core metapackage + - authselect-compat + - langpacks-en + - tuned + +- Services + - nm-cloud-setup.service + - nm-cloud-setup.timer + - tuned diff --git a/chatbot/training/cloud/aws/cloud-setup.sh b/chatbot/training/cloud/aws/cloud-setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..7beb2cbfa018e325a7d1b18dc6832df1a73d309f --- /dev/null +++ b/chatbot/training/cloud/aws/cloud-setup.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +set -o errexit + +mv /etc/selinux /etc/selinux.tmp +dnf install -y --nobest \ + cloud-init \ + langpacks-en \ + tuned +mv /etc/selinux.tmp /etc/selinux + +# Chrony configuration +sed -i \ + -e '/^pool /c\server 169.254.169.123 prefer iburst minpoll 4 maxpoll 4' \ + -e '/^leapsectz /d' \ + /etc/chrony.conf diff --git a/chatbot/training/cloud/aws/config.toml b/chatbot/training/cloud/aws/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..8c0379da99d2c0eecc9fcdfd58cdafde0bbd7a97 --- /dev/null +++ b/chatbot/training/cloud/aws/config.toml @@ -0,0 +1,4 @@ +[customizations.kernel] +name = "customizations-for-aws" +append = "console=ttyS0,115200n8 net.ifnames=0 nvme_core.io_timeout=4294967295" + diff --git a/chatbot/training/cloud/aws/files/etc/X11/xorg.conf.d/00-keyboard.conf b/chatbot/training/cloud/aws/files/etc/X11/xorg.conf.d/00-keyboard.conf new file mode 100644 index 0000000000000000000000000000000000000000..c5fe1721b6dd54af64a971419d5e6dd91b8a8901 --- /dev/null +++ b/chatbot/training/cloud/aws/files/etc/X11/xorg.conf.d/00-keyboard.conf @@ -0,0 +1,6 @@ +# Do not edit manually, use localectl(1). +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" +EndSection diff --git a/chatbot/training/cloud/aws/files/etc/cloud/cloud.cfg.d/00-rhel-default-user.cfg b/chatbot/training/cloud/aws/files/etc/cloud/cloud.cfg.d/00-rhel-default-user.cfg new file mode 100644 index 0000000000000000000000000000000000000000..996064ce8d42873edb869e3edd2d97bfdb770ced --- /dev/null +++ b/chatbot/training/cloud/aws/files/etc/cloud/cloud.cfg.d/00-rhel-default-user.cfg @@ -0,0 +1,3 @@ +system_info: + default_user: + name: ec2-user diff --git a/chatbot/training/cloud/aws/files/etc/locale.conf b/chatbot/training/cloud/aws/files/etc/locale.conf new file mode 100644 index 0000000000000000000000000000000000000000..01ec548f82205efd53e4fc1be27aef47ddaee9cc --- /dev/null +++ b/chatbot/training/cloud/aws/files/etc/locale.conf @@ -0,0 +1 @@ +LANG=en_US.UTF-8 diff --git a/chatbot/training/cloud/aws/files/etc/systemd/system/sshd-keygen@.service.d/disable-sshd-keygen-if-cloud-init-active.conf b/chatbot/training/cloud/aws/files/etc/systemd/system/sshd-keygen@.service.d/disable-sshd-keygen-if-cloud-init-active.conf new file mode 100644 index 0000000000000000000000000000000000000000..1a5d7a5a6f037f9a093a733c4dc81ad6f563b0a3 --- /dev/null +++ b/chatbot/training/cloud/aws/files/etc/systemd/system/sshd-keygen@.service.d/disable-sshd-keygen-if-cloud-init-active.conf @@ -0,0 +1,7 @@ +# In some cloud-init enabled images the sshd-keygen template service may race +# with cloud-init during boot causing issues with host key generation. This +# drop-in config adds a condition to sshd-keygen@.service if it exists and +# prevents the sshd-keygen units from running *if* cloud-init is going to run. +# +[Unit] +ConditionPathExists=!/run/systemd/generator.early/multi-user.target.wants/cloud-init.target diff --git a/chatbot/training/cloud/aws/files/etc/vconsole.conf b/chatbot/training/cloud/aws/files/etc/vconsole.conf new file mode 100644 index 0000000000000000000000000000000000000000..12b81a1b292b62f38916d15c11551c6254dbac2c --- /dev/null +++ b/chatbot/training/cloud/aws/files/etc/vconsole.conf @@ -0,0 +1 @@ +KEYMAP=us diff --git a/chatbot/training/cloud/aws/files/usr/lib/bootc/install/05-cloud-kargs.toml b/chatbot/training/cloud/aws/files/usr/lib/bootc/install/05-cloud-kargs.toml new file mode 100644 index 0000000000000000000000000000000000000000..7f1b54ee67399b7c967dae8e216dda64a6a41237 --- /dev/null +++ b/chatbot/training/cloud/aws/files/usr/lib/bootc/install/05-cloud-kargs.toml @@ -0,0 +1,2 @@ +[install] +kargs = ["console=tty0", "console=ttyS0,115200n8", "net.ifnames=0", "nvme_core.io_timeout=4294967295"] diff --git a/chatbot/training/cloud/aws/files/usr/lib/systemd/logind.conf.d/00-getty-fixes.conf b/chatbot/training/cloud/aws/files/usr/lib/systemd/logind.conf.d/00-getty-fixes.conf new file mode 100644 index 0000000000000000000000000000000000000000..7758eb2ad82b0802703443eb7a6f9f17c5e56529 --- /dev/null +++ b/chatbot/training/cloud/aws/files/usr/lib/systemd/logind.conf.d/00-getty-fixes.conf @@ -0,0 +1,3 @@ +[Login] +NAutoVTs=0 + diff --git a/chatbot/training/cloud/aws/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-ec2.conf b/chatbot/training/cloud/aws/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-ec2.conf new file mode 100644 index 0000000000000000000000000000000000000000..0eb3821977c1f92ce8ae61cf734ec0070fca6bff --- /dev/null +++ b/chatbot/training/cloud/aws/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-ec2.conf @@ -0,0 +1,2 @@ +[Service] +Environment="NM_CLOUD_SETUP_EC2=yes" diff --git a/chatbot/training/cloud/azure/Makefile.env b/chatbot/training/cloud/azure/Makefile.env new file mode 100644 index 0000000000000000000000000000000000000000..5f7f07c95ce39d0395259103e35e85d36aa8a6ca --- /dev/null +++ b/chatbot/training/cloud/azure/Makefile.env @@ -0,0 +1 @@ +DISK_TYPE=raw diff --git a/chatbot/training/cloud/azure/README.md b/chatbot/training/cloud/azure/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f159c3cca32bab965351c0c9c57ee0795dfeee48 --- /dev/null +++ b/chatbot/training/cloud/azure/README.md @@ -0,0 +1,60 @@ +# Azure for RHEL AI +Trying to mimic as much as possible the [changes on RHEL for Azure](https://github.com/osbuild/images/blob/main/pkg/distro/rhel/rhel9/azure.go) + +# Summary +- Extra kernel parameters + +Even if in the link [Kernel Parameters on RHEL for Azure](https://github.com/osbuild/images/blob/a4ae81dc3eed3e86c359635e3135fc8a07f411dd/pkg/distro/rhel/rhel9/azure.go#L454) we see other changes, when running a RHEL instance in Azure, the extra kernel parameters are others, so we will take those as our reference +``` +loglevel=3 console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0,115200 net.ifnames=0 cloud-init=disabled +``` + +Note that we also disable cloud-init via kernel parameter + +- Timezone: UTC +- Locale: en_US.UTF-8 +- Keymap: us +- X11 layout: us + +- sshd config + - ClientAliveInterval: 180 + +- Packages + - hyperv-daemons + - langpacks-en + - NetworkManager-cloud-setup + - nvme-cli + - patch + - rng-tools + - uuid + - WALinuxAgent + +- Services + - nm-cloud-setup.service + - nm-cloud-setup.timer + - waagent + +- Systemd + - nm-cloud-setup.service: `Environment=NM_CLOUD_SETUP_AZURE=yes` + +- Kernel Modules + - blacklist amdgpu + - blacklist intel_cstate + - blacklist floppy + - blacklist nouveau + - blacklist lbm-nouveau + - blacklist skx_edac + +- Cloud Init + - 10-azure-kvp.cfg + - 91-azure_datasource.cfg + +- PwQuality + - /etc/security/pwquality.conf + +- WaAgentConfig + - RDFormat false + - RDEnableSwap false + +- udev rules + - /etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules diff --git a/chatbot/training/cloud/azure/cloud-setup.sh b/chatbot/training/cloud/azure/cloud-setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..2b5e87d4004586678807470729b10e293bfaddb4 --- /dev/null +++ b/chatbot/training/cloud/azure/cloud-setup.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -o errexit + +mv /etc/selinux /etc/selinux.tmp +dnf install -y --nobest \ + cloud-init \ + hyperv-daemons \ + langpacks-en \ + NetworkManager-cloud-setup \ + nvme-cli \ + patch \ + rng-tools \ + uuid \ + WALinuxAgent +mv /etc/selinux.tmp /etc/selinux + +# sshd configuration +cat << EOF >> /etc/ssh/sshd_config +ClientAliveInterval 180 +EOF + +# pwquality configuration +cat << EOF >> /etc/security/pwquality.conf +minlen = 6 +dcredit = 0 +ucredit = 0 +lcredit = 0 +ocredit = 0 +minclass = 3 +EOF + +# WAAgent configuration +sed -i \ + -e '/^ResourceDisk.Format=y/c\ResourceDisk.Format=n' \ + -e '/^ResourceDisk.EnableSwap=y/c\ResourceDisk.EnableSwap=n' \ + -e '/^Provisioning.RegenerateSshHostKeyPair=y/c\Provisioning.RegenerateSshHostKeyPair=n' \ + /etc/waagent.conf diff --git a/chatbot/training/cloud/azure/config.toml b/chatbot/training/cloud/azure/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..dabf9cdace2985e80a5fa8d7b2f7c1cdeac24494 --- /dev/null +++ b/chatbot/training/cloud/azure/config.toml @@ -0,0 +1,6 @@ +[customizations.kernel] +name = "customizations-for-azure" +# This is suggested by https://github.com/osbuild/images/blob/a4ae81dc3eed3e86c359635e3135fc8a07f411dd/pkg/distro/rhel/rhel9/azure.go#L454 +# append = "ro loglevel=3 console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300" +# However, starting a RHEL instance in azure shows this one, and I'll be using it +append = "loglevel=3 console=tty1 console=ttyS0,115200n8 earlyprintk=ttyS0,115200 net.ifnames=0" diff --git a/chatbot/training/cloud/azure/files/etc/X11/xorg.conf.d/00-keyboard.conf b/chatbot/training/cloud/azure/files/etc/X11/xorg.conf.d/00-keyboard.conf new file mode 100644 index 0000000000000000000000000000000000000000..c5fe1721b6dd54af64a971419d5e6dd91b8a8901 --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/X11/xorg.conf.d/00-keyboard.conf @@ -0,0 +1,6 @@ +# Do not edit manually, use localectl(1). +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" +EndSection diff --git a/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/10-azure-kvp.cfg b/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/10-azure-kvp.cfg new file mode 100644 index 0000000000000000000000000000000000000000..cbc0f7471a518484171c4e10a0401ae09d75d524 --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/10-azure-kvp.cfg @@ -0,0 +1,6 @@ +# This configuration file is used to enable logging to Hyper-V kvp +reporting: + logging: + type: log + telemetry: + type: hyperv diff --git a/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/91-azure_datasource.cfg b/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/91-azure_datasource.cfg new file mode 100644 index 0000000000000000000000000000000000000000..39b3c69395162c71819ccfab0eaed5b58e3ef0d3 --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/cloud/cloud.cfg.d/91-azure_datasource.cfg @@ -0,0 +1,4 @@ +datasource_list: [ Azure ] +datasource: + Azure: + apply_network_config: False diff --git a/chatbot/training/cloud/azure/files/etc/locale.conf b/chatbot/training/cloud/azure/files/etc/locale.conf new file mode 100644 index 0000000000000000000000000000000000000000..01ec548f82205efd53e4fc1be27aef47ddaee9cc --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/locale.conf @@ -0,0 +1 @@ +LANG=en_US.UTF-8 diff --git a/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-floppy.conf b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-floppy.conf new file mode 100644 index 0000000000000000000000000000000000000000..81e9704ed55d0e9948d43b068fc149edeced4460 --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-floppy.conf @@ -0,0 +1 @@ +blacklist floppy diff --git a/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-intel-cstate.conf b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-intel-cstate.conf new file mode 100644 index 0000000000000000000000000000000000000000..4bbfb046948042589bb383ec18bdd8d53e6692de --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-intel-cstate.conf @@ -0,0 +1 @@ +blacklist intel_cstate diff --git a/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-nouveau.conf b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-nouveau.conf new file mode 100644 index 0000000000000000000000000000000000000000..3a21cfefabd4e327be341f9488c9d9138f96692d --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-nouveau.conf @@ -0,0 +1,2 @@ +blacklist nouveau +blacklist lbm-nouveau diff --git a/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-skylake-edac.conf b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-skylake-edac.conf new file mode 100644 index 0000000000000000000000000000000000000000..7a8bdda3f3e14e9c91be911f62f36293afcd724b --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/modprobe.d/blacklist-skylake-edac.conf @@ -0,0 +1 @@ +blacklist skx_edac diff --git a/chatbot/training/cloud/azure/files/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules b/chatbot/training/cloud/azure/files/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules new file mode 100644 index 0000000000000000000000000000000000000000..59cf73bb0a424596836dd13efb4e0ea2f4d6aefd --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules @@ -0,0 +1,4 @@ +# Accelerated Networking on Azure exposes a new SRIOV interface to the VM. +# This interface is transparently bonded to the synthetic interface, +# so NetworkManager should just ignore any SRIOV interfaces. +SUBSYSTEM=="net", DRIVERS=="hv_pci", ACTION=="add", ENV{NM_UNMANAGED}="1" diff --git a/chatbot/training/cloud/azure/files/etc/vconsole.conf b/chatbot/training/cloud/azure/files/etc/vconsole.conf new file mode 100644 index 0000000000000000000000000000000000000000..12b81a1b292b62f38916d15c11551c6254dbac2c --- /dev/null +++ b/chatbot/training/cloud/azure/files/etc/vconsole.conf @@ -0,0 +1 @@ +KEYMAP=us diff --git a/chatbot/training/cloud/azure/files/usr/lib/bootc/install/05-cloud-kargs.toml b/chatbot/training/cloud/azure/files/usr/lib/bootc/install/05-cloud-kargs.toml new file mode 100644 index 0000000000000000000000000000000000000000..d221eaac8697f971e83c3aeb7d054dcfe565d2f5 --- /dev/null +++ b/chatbot/training/cloud/azure/files/usr/lib/bootc/install/05-cloud-kargs.toml @@ -0,0 +1,5 @@ +[install] +# This is suggested by https://github.com/osbuild/images/blob/a4ae81dc3eed3e86c359635e3135fc8a07f411dd/pkg/distro/rhel/rhel9/azure.go#L454 +# kargs = ["ro", "loglevel=3", "console=tty1", "console=ttyS0", "earlyprintk=ttyS0", "rootdelay=300"] +# However, starting a RHEL instance in azure shows this one, and I'll be using it +kargs = ["loglevel=3", "console=tty1", "console=ttyS0,115200n8", "earlyprintk=ttyS0,115200", "net.ifnames=0"] diff --git a/chatbot/training/cloud/azure/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-azure.conf b/chatbot/training/cloud/azure/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-azure.conf new file mode 100644 index 0000000000000000000000000000000000000000..2cb23781cfd744ac1c07a5241d1b4b8ba096ad64 --- /dev/null +++ b/chatbot/training/cloud/azure/files/usr/lib/systemd/system/nm-cloud-setup.service.d/10-rh-enable-for-azure.conf @@ -0,0 +1,2 @@ +[Service] +Environment="NM_CLOUD_SETUP_AZURE=yes" diff --git a/chatbot/training/cloud/gcp/Makefile.env b/chatbot/training/cloud/gcp/Makefile.env new file mode 100644 index 0000000000000000000000000000000000000000..5f7f07c95ce39d0395259103e35e85d36aa8a6ca --- /dev/null +++ b/chatbot/training/cloud/gcp/Makefile.env @@ -0,0 +1 @@ +DISK_TYPE=raw diff --git a/chatbot/training/cloud/gcp/README.md b/chatbot/training/cloud/gcp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..813e975b26767cd2a8ae4b893d912d86c9dea7c2 --- /dev/null +++ b/chatbot/training/cloud/gcp/README.md @@ -0,0 +1,43 @@ +# Google Cloud Platform modifications for RHEL AI +Trying to mimic as much as possible the [changes on RHEL for GCP](https://github.com/osbuild/images/blob/main/pkg/distro/rhel/rhel9/gce.go) + +## Changes + +- Extra kernel parameters + +``` +net.ifnames=0 biosdevname=0 scsi_mod.use_blk_mq=Y console=ttyS0,38400n8d +``` + +- Timezone: UTC +- Chrony configuration: + - Change server +- Locale: en_US.UTF-8 +- Keymap: us +- X11 layout: us + +- sshd config + - PasswordAuthentication: false + - ClientAliveInterval: 420 + - PermitRootLogin: No + +- Modules + - blacklist floppy + +- GCPGuestAgentConfig + - SetBotoConfig: false + +- Packages + - langpacks-en + - acpid + - rng-tools + - vim + - google-compute-engine + - google-osconfig-agent + - gce-disk-expand + - timedatex + - tuned + +- Remove Packages + - irqbalance + - microcode_ctl diff --git a/chatbot/training/cloud/gcp/cloud-setup.sh b/chatbot/training/cloud/gcp/cloud-setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..a183e8170dd06f511697ef4b0bf9f70846b0a442 --- /dev/null +++ b/chatbot/training/cloud/gcp/cloud-setup.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -o errexit + +eval $(grep VERSION_ID /etc/os-release) +tee /etc/yum.repos.d/google-cloud.repo << EOF +[google-compute-engine] +name=Google Compute Engine +baseurl=https://packages.cloud.google.com/yum/repos/google-compute-engine-el${VERSION_ID/.*}-x86_64-stable +enabled=1 +gpgcheck=1 +repo_gpgcheck=0 +gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg + https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg +EOF + +mv /etc/selinux /etc/selinux.tmp +dnf install -y --nobest \ + acpid \ + cloud-init \ + google-compute-engine \ + google-osconfig-agent \ + langpacks-en \ + rng-tools \ + timedatex \ + tuned \ + vim +mv /etc/selinux.tmp /etc/selinux + +# The current version of google-cloud-ops-agent is impacted by a CVE: https://access.redhat.com/security/cve/CVE-2024-41110 +# It will be disable for the meantime +# +# # Install Google Ops Agent +# curl -sSo /tmp/add-google-cloud-ops-agent-repo.sh https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh +# bash /tmp/add-google-cloud-ops-agent-repo.sh --also-install --remove-repo +# rm /tmp/add-google-cloud-ops-agent-repo.sh + +# rpm-state is needed to remove microcode_ctl +mkdir /var/lib/rpm-state +dnf remove -y \ + irqbalance \ + microcode_ctl +rmdir /var/lib/rpm-state + +rm -f /etc/yum.repos.d/google-cloud.repo + +# Chrony configuration +sed -i \ + -e '/^pool /c\server metadata.google.internal iburst' \ + /etc/chrony.conf + +# sshd configuration +cat << EOF >> /etc/ssh/sshd_config +PermitRootLogin no +PasswordAuthentication no +ClientAliveInterval 420 +EOF diff --git a/chatbot/training/cloud/gcp/config.toml b/chatbot/training/cloud/gcp/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..fb1a1581c19eb7ad47481a4b80fd705d0b4dd44b --- /dev/null +++ b/chatbot/training/cloud/gcp/config.toml @@ -0,0 +1,4 @@ +[customizations.kernel] +name = "customizations-for-gcp" +append = "net.ifnames=0 biosdevname=0 scsi_mod.use_blk_mq=Y console=ttyS0,38400n8d cloud-init=disabled" + diff --git a/chatbot/training/cloud/gcp/files/etc/X11/xorg.conf.d/00-keyboard.conf b/chatbot/training/cloud/gcp/files/etc/X11/xorg.conf.d/00-keyboard.conf new file mode 100644 index 0000000000000000000000000000000000000000..c5fe1721b6dd54af64a971419d5e6dd91b8a8901 --- /dev/null +++ b/chatbot/training/cloud/gcp/files/etc/X11/xorg.conf.d/00-keyboard.conf @@ -0,0 +1,6 @@ +# Do not edit manually, use localectl(1). +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" +EndSection diff --git a/chatbot/training/cloud/gcp/files/etc/default/instance_configs.cfg b/chatbot/training/cloud/gcp/files/etc/default/instance_configs.cfg new file mode 100644 index 0000000000000000000000000000000000000000..cf3b158678dfdb6ec3c93bb8f65f1b620369d974 --- /dev/null +++ b/chatbot/training/cloud/gcp/files/etc/default/instance_configs.cfg @@ -0,0 +1,3 @@ +# Disable boto plugin setup. +[InstanceSetup] +set_boto_config = false diff --git a/chatbot/training/cloud/gcp/files/etc/locale.conf b/chatbot/training/cloud/gcp/files/etc/locale.conf new file mode 100644 index 0000000000000000000000000000000000000000..01ec548f82205efd53e4fc1be27aef47ddaee9cc --- /dev/null +++ b/chatbot/training/cloud/gcp/files/etc/locale.conf @@ -0,0 +1 @@ +LANG=en_US.UTF-8 diff --git a/chatbot/training/cloud/gcp/files/etc/modprobe.d/blacklist-floppy.conf b/chatbot/training/cloud/gcp/files/etc/modprobe.d/blacklist-floppy.conf new file mode 100644 index 0000000000000000000000000000000000000000..81e9704ed55d0e9948d43b068fc149edeced4460 --- /dev/null +++ b/chatbot/training/cloud/gcp/files/etc/modprobe.d/blacklist-floppy.conf @@ -0,0 +1 @@ +blacklist floppy diff --git a/chatbot/training/cloud/gcp/files/etc/vconsole.conf b/chatbot/training/cloud/gcp/files/etc/vconsole.conf new file mode 100644 index 0000000000000000000000000000000000000000..12b81a1b292b62f38916d15c11551c6254dbac2c --- /dev/null +++ b/chatbot/training/cloud/gcp/files/etc/vconsole.conf @@ -0,0 +1 @@ +KEYMAP=us diff --git a/chatbot/training/cloud/gcp/files/usr/lib/bootc/install/05-cloud-kargs.toml b/chatbot/training/cloud/gcp/files/usr/lib/bootc/install/05-cloud-kargs.toml new file mode 100644 index 0000000000000000000000000000000000000000..c940e5a9117f8335704cf0cb8227b3beac034235 --- /dev/null +++ b/chatbot/training/cloud/gcp/files/usr/lib/bootc/install/05-cloud-kargs.toml @@ -0,0 +1,2 @@ +[install] +kargs = ["net.ifnames=0", "biosdevname=0", "scsi_mod.use_blk_mq=Y", "console=ttyS0,38400n8d", "cloud-init=disabled"] diff --git a/chatbot/training/cloud/ibm/Makefile.env b/chatbot/training/cloud/ibm/Makefile.env new file mode 100644 index 0000000000000000000000000000000000000000..0421537144f609d29dbb359af5e9c00ad7804c78 --- /dev/null +++ b/chatbot/training/cloud/ibm/Makefile.env @@ -0,0 +1 @@ +DISK_TYPE=qcow2 diff --git a/chatbot/training/cloud/ibm/README.md b/chatbot/training/cloud/ibm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..97cef5f2876e3bb5bb89889ad9ef48f04744f5a0 --- /dev/null +++ b/chatbot/training/cloud/ibm/README.md @@ -0,0 +1,14 @@ +# IBM Cloud modifications for RHEL AI + +## Changes + +- Timezone: UTC +- Chrony configuration: + - Change server + - LeapsecTz +- Locale: en_US.UTF-8 +- Keymap: us +- X11 layout: us + +- Packages + - langpacks-en diff --git a/chatbot/training/cloud/ibm/cloud-setup.sh b/chatbot/training/cloud/ibm/cloud-setup.sh new file mode 100644 index 0000000000000000000000000000000000000000..e1e51a26a23af0319237732e33d03d681d7077be --- /dev/null +++ b/chatbot/training/cloud/ibm/cloud-setup.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit + +mv /etc/selinux /etc/selinux.tmp +dnf install -y --nobest \ + cloud-init \ + langpacks-en +mv /etc/selinux.tmp /etc/selinux diff --git a/chatbot/training/cloud/ibm/config.toml b/chatbot/training/cloud/ibm/config.toml new file mode 100644 index 0000000000000000000000000000000000000000..e6f4ee74e1352b37c95477ccef8a172366aa4386 --- /dev/null +++ b/chatbot/training/cloud/ibm/config.toml @@ -0,0 +1,2 @@ +[customizations.kernel] + diff --git a/chatbot/training/cloud/ibm/files/etc/X11/xorg.conf.d/00-keyboard.conf b/chatbot/training/cloud/ibm/files/etc/X11/xorg.conf.d/00-keyboard.conf new file mode 100644 index 0000000000000000000000000000000000000000..c5fe1721b6dd54af64a971419d5e6dd91b8a8901 --- /dev/null +++ b/chatbot/training/cloud/ibm/files/etc/X11/xorg.conf.d/00-keyboard.conf @@ -0,0 +1,6 @@ +# Do not edit manually, use localectl(1). +Section "InputClass" + Identifier "system-keyboard" + MatchIsKeyboard "on" + Option "XkbLayout" "us" +EndSection diff --git a/chatbot/training/cloud/ibm/files/etc/locale.conf b/chatbot/training/cloud/ibm/files/etc/locale.conf new file mode 100644 index 0000000000000000000000000000000000000000..01ec548f82205efd53e4fc1be27aef47ddaee9cc --- /dev/null +++ b/chatbot/training/cloud/ibm/files/etc/locale.conf @@ -0,0 +1 @@ +LANG=en_US.UTF-8 diff --git a/chatbot/training/cloud/ibm/files/etc/vconsole.conf b/chatbot/training/cloud/ibm/files/etc/vconsole.conf new file mode 100644 index 0000000000000000000000000000000000000000..12b81a1b292b62f38916d15c11551c6254dbac2c --- /dev/null +++ b/chatbot/training/cloud/ibm/files/etc/vconsole.conf @@ -0,0 +1 @@ +KEYMAP=us diff --git a/chatbot/training/common/Makefile.common b/chatbot/training/common/Makefile.common new file mode 100644 index 0000000000000000000000000000000000000000..a08d68e7ff66311f97bc2d7a9dd2862cc0354e64 --- /dev/null +++ b/chatbot/training/common/Makefile.common @@ -0,0 +1,127 @@ +FROM ?= +HARDWARE ?= + +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +IMAGE_NAME ?= $(HARDWARE)-bootc +IMAGE_TAG ?= latest +BOOTC_IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}:${IMAGE_TAG} +BOOTC_MODELS_IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}-models:${IMAGE_TAG} +BOOTC_CLOUD_IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}-cloud:${IMAGE_TAG} +BOOTC_CLOUD_MODELS_IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}-cloud-models:${IMAGE_TAG} +FROM_BOOTC_IMAGE ?= $(BOOTC_IMAGE) +VENDOR ?= + +CONTAINER_TOOL ?= podman +CONTAINER_TOOL_EXTRA_ARGS ?= +EXTRA_RPM_PACKAGES ?= +GRAPH_ROOT=$(shell podman info --format '{{ .Store.GraphRoot }}') +UMASK=$(shell umask) +IMAGE_VERSION_ID := $(or ${IMAGE_VERSION_ID},$(shell git rev-parse --short HEAD)) + +AUTH_JSON ?= + +SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct) + +BOOTC_IMAGE_BUILDER ?= quay.io/centos-bootc/bootc-image-builder +BUILD_ARG_FILE ?= +IMAGE_BUILDER_CONFIG ?= +IMAGE_BUILDER_EXTRA_ARGS ?= +DISK_TYPE ?= qcow2 +DISK_UID ?= $(shell id -u) +DISK_GID ?= $(shell id -g) + +ARCH ?= $(shell arch) + +DRIVER_VERSION ?= +KERNEL_VERSION ?= $(shell skopeo inspect --format json docker://${FROM} | jq -r '.Labels["ostree.linux"]' | sed "s/\.${ARCH}//") + +INSTRUCTLAB_IMAGE ?= $(REGISTRY)/$(REGISTRY_ORG)/instructlab-$(HARDWARE):$(IMAGE_TAG) +WRAPPER = $(CURDIR)/../ilab-wrapper/ilab +QLORA_WRAPPER = $(CURDIR)/../ilab-wrapper/ilab-qlora +TRAIN_WRAPPER = $(CURDIR)/../ilab-wrapper/ilab-training-launcher +OUTDIR = $(CURDIR)/../build +MODELS_CONTAINERFILE = $(OUTDIR)/Containerfile.models + +DRIVER_TOOLKIT_BASE_IMAGE ?= +DRIVER_TOOLKIT_IMAGE_NAME ?= driver-toolkit +DRIVER_TOOLKIT_IMAGE_TAG ?= ${KERNEL_VERSION} +DRIVER_TOOLKIT_IMAGE ?= ${REGISTRY}/${REGISTRY_ORG}/${DRIVER_TOOLKIT_IMAGE_NAME}:${DRIVER_TOOLKIT_IMAGE_TAG} + +ENABLE_RT ?= + +SSH_PUBKEY ?= $(shell cat ${HOME}/.ssh/id_rsa.pub 2> /dev/null) + +.PHONY: prepare-files +prepare-files: $(OUTDIR)/$(WRAPPER) $(OUTDIR)/$(QLORA_WRAPPER) $(OUTDIR)/$(TRAIN_WRAPPER) $(OUTDIR) common-services + +$(OUTDIR): + mkdir -p $(OUTDIR) +$(OUTDIR)/$(WRAPPER): $(OUTDIR) + cp -pf $(WRAPPER) $(OUTDIR) +$(OUTDIR)/$(QLORA_WRAPPER): $(OUTDIR) + cp -pf $(QLORA_WRAPPER) $(OUTDIR) +$(OUTDIR)/$(TRAIN_WRAPPER): $(OUTDIR) + cp -pf $(TRAIN_WRAPPER) $(OUTDIR) + +.PHONY: common-services +common-services: + mkdir -p build; cp -pR ../common/usr build + + +.PHONY: check-sshkey +check-sshkey: + @test -n "$(SSH_PUBKEY)" || \ + (echo -n "Error: no ssh key defined! "; \ + echo "Create ~/.ssh/id_rsa.pub or set SSH_PUBKEY"; exit 1) + +.PHONY: push +push: + podman push "${REGISTRY}/${REGISTRY_ORG}/${IMAGE_NAME}:${IMAGE_TAG}" + +.PHONY: check-umask +check-umask: + @test "$(UMASK)" = "0022" || \ + (echo; echo -n "Error: umask $(UMASK) will cause unexpected behaviour: use umask 022! "; \ + echo "Verify the `ai-lab-recipes` git repository was cloned with umask 0022"; exit 1) + +.PHONY: bootc-image-builder +bootc-image-builder: + mkdir -p build/store build/output + podman run \ + $(AUTH_JSON:%=-v %:/run/containers/0/auth.json) \ + --rm \ + -ti \ + --privileged \ + --pull newer \ + -v $(GRAPH_ROOT):/var/lib/containers/storage \ + -v ./build/store:/store \ + -v ./build/output:/output \ + $(IMAGE_BUILDER_CONFIG:%=-v %:/config$(suffix $(IMAGE_BUILDER_CONFIG))) \ + ${CONTAINER_TOOL_EXTRA_ARGS} \ + $(BOOTC_IMAGE_BUILDER) \ + $(IMAGE_BUILDER_CONFIG:%=--config /config$(suffix $(IMAGE_BUILDER_CONFIG))) \ + ${IMAGE_BUILDER_EXTRA_ARGS} \ + --chown $(DISK_UID):$(DISK_GID) \ + --local \ + --type $(DISK_TYPE) \ + $(ARCH:%=--target-arch %) \ + $(BOOTC_IMAGE) + +.PHONY: driver-toolkit +driver-toolkit: + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(ENABLE_RT:%=--build-arg ENABLE_RC=%) \ + $(FROM:%=--build-arg BASEIMAGE=%) \ + $(DRIVER_TOOLKIT_BASE_IMAGE:%=--build-arg BASEIMAGE=%) \ + $(KERNEL_VERSION:%=--build-arg KERNEL_VERSION=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + --file ../common/driver-toolkit/Containerfile \ + --tag "${DRIVER_TOOLKIT_IMAGE}" \ + ${CONTAINER_TOOL_EXTRA_ARGS} . + +.PHONY: clean +clean: + rm -rf build diff --git a/chatbot/training/common/driver-toolkit/Containerfile b/chatbot/training/common/driver-toolkit/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..3d570c07010eff112267c1b8abc33827c8394d5b --- /dev/null +++ b/chatbot/training/common/driver-toolkit/Containerfile @@ -0,0 +1,68 @@ +ARG BASEIMAGE="quay.io/centos/centos:stream9" + +FROM ${BASEIMAGE} + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +ARG KERNEL_VERSION='' +ARG ENABLE_RT='' + +USER root + +# The need for the `cp /etc/dnf/dnf.conf` is a workaround for https://github.com/containers/bootc/issues/637 +RUN if test -z "${KERNEL_VERSION}" ; then \ + echo "The KERNEL_VERSION argument is mandatory. Exiting" ; \ + exit 1 ; \ + fi \ + && echo "Kernel version: ${KERNEL_VERSION}" \ + && mv /etc/selinux /etc/selinux.tmp \ + && dnf -y install dnf-plugin-config-manager \ + && cp -a /etc/dnf/dnf.conf{,.tmp} && mv /etc/dnf/dnf.conf{.tmp,} \ + && dnf config-manager --best --nodocs --setopt=install_weak_deps=False --save \ + && dnf -y update --exclude kernel* \ + && dnf -y install \ + kernel-${KERNEL_VERSION} \ + kernel-devel-${KERNEL_VERSION} \ + kernel-modules-${KERNEL_VERSION} \ + kernel-modules-extra-${KERNEL_VERSION} \ + && if [ "${ENABLE_RT}" ] && [ $(arch) == "x86_64" ]; then \ + dnf -y --enablerepo=rt install \ + kernel-rt-${KERNEL_VERSION} \ + kernel-rt-devel-${KERNEL_VERSION} \ + kernel-rt-modules-${KERNEL_VERSION} \ + kernel-rt-modules-extra-${KERNEL_VERSION}; \ + fi \ + && export INSTALLED_KERNEL=$(rpm -q --qf "%{VERSION}-%{RELEASE}.%{ARCH}" kernel-core-${KERNEL_VERSION}) \ + && export GCC_VERSION=$(cat /lib/modules/${INSTALLED_KERNEL}/config | grep -Eo "gcc \(GCC\) ([0-9\.]+)" | grep -Eo "([0-9\.]+)") \ + && dnf -y install \ + binutils \ + diffutils \ + elfutils-libelf-devel \ + jq \ + kabi-dw kernel-abi-stablelists \ + keyutils \ + kmod \ + gcc-${GCC_VERSION} \ + git \ + make \ + mokutil \ + openssl \ + pinentry \ + rpm-build \ + xz \ + && dnf clean all \ + && mv /etc/selinux.tmp /etc/selinux \ + && useradd -u 1001 -m -s /bin/bash builder + +# Last layer for metadata for mapping the driver-toolkit to a specific kernel version +RUN if [ "${KERNEL_VERSION}" == "" ]; then \ + export INSTALLED_KERNEL=$(rpm -q --qf "%{VERSION}-%{RELEASE}.%{ARCH}" kernel-core); \ + else \ + export INSTALLED_KERNEL=$(rpm -q --qf "%{VERSION}-%{RELEASE}.%{ARCH}" kernel-core-${KERNEL_VERSION}) ;\ + fi \ + && echo "{ \"KERNEL_VERSION\": \"${INSTALLED_KERNEL}\" }" > /etc/driver-toolkit-release.json \ + && echo -e "KERNEL_VERSION=\"${INSTALLED_KERNEL}\"" > /etc/driver-toolkit-release.sh + +USER builder diff --git a/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.service b/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.service new file mode 100644 index 0000000000000000000000000000000000000000..1e479959d09150e8f2e93a4e8cc84bd3b4b4968c --- /dev/null +++ b/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.service @@ -0,0 +1,12 @@ +[Unit] +Description=Check for available RHEL AI upgrade +ConditionPathExists=/run/ostree-booted +After=network-online.target +StartLimitIntervalSec=400 +StartLimitBurst=3 + +[Service] +Type=oneshot +ExecStart=/usr/libexec/upgrade-informer +Restart=on-failure +RestartSec=90 diff --git a/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.timer b/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.timer new file mode 100644 index 0000000000000000000000000000000000000000..229db9faa6ee71810a62fbf4d6e33686bab562fd --- /dev/null +++ b/chatbot/training/common/usr/lib/systemd/system/upgrade-informer.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Runs upgrade informer periodically +ConditionPathExists=/run/ostree-booted + +[Timer] +OnBootSec=1h +OnUnitInactiveSec=1day +RandomizedDelaySec=2h + +[Install] +WantedBy=timers.target diff --git a/chatbot/training/common/usr/libexec/upgrade-informer b/chatbot/training/common/usr/libexec/upgrade-informer new file mode 100644 index 0000000000000000000000000000000000000000..5f8979c78343509535036076e6ae191b70aad987 --- /dev/null +++ b/chatbot/training/common/usr/libexec/upgrade-informer @@ -0,0 +1,37 @@ +#!/bin/bash + +# Run the command and capture its output +output=$(bootc upgrade --check | sed -e 1q) +message_file="/etc/motd.d/upgrade-message" +bootc_auth="/etc/ostree/auth.json" + +if [[ $output == Update\ available* ]]; then + if [[ ! -f $message_file ]]; then + echo "New version was found" + bootc_image=$(awk '{print $4}' <<< "$output") + # If auth file exists we should use it + auth_params="" + if [[ -f $bootc_auth ]]; then + auth_params="--authfile $bootc_auth" + fi + + # Get image version + # shellcheck disable=SC2086 + image_version_id=$(skopeo inspect --format json $auth_params "$bootc_image" | jq -r '.Labels | .["image_version_id"] // empty') + + # If upgrade available, write the output to the file + cat > $message_file << EOF + +** Attention! ** +** A new $image_version_id version is available ** +** In order to apply it run: bootc upgrade --apply +** Please note that the system will reboot after the upgrade ** + +EOF + fi +else + echo "No upgrade was found" + rm $message_file 2> /dev/null +fi + +echo "Finished running upgrade informer" diff --git a/chatbot/training/deepspeed/Containerfile b/chatbot/training/deepspeed/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..e79b733a57a817c91c457db6ee49b3d45a2bd9b4 --- /dev/null +++ b/chatbot/training/deepspeed/Containerfile @@ -0,0 +1,21 @@ +# Containerfile for running deepspeed training +FROM nvcr.io/nvidia/cuda:12.1.1-cudnn8-devel-ubi9 + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +RUN dnf install -y python python-devel git +RUN python -m ensurepip --upgrade +RUN pip3 install torch==2.1.2 --index-url https://download.pytorch.org/whl/cu121 +RUN pip3 install packaging wheel +RUN pip3 install flash-attn==2.5.7 +RUN pip3 install deepspeed==0.14.2 +RUN pip3 install transformers==4.40.1 +RUN pip3 install ipdb jupyterlab gpustat matplotlib hydra-core datasets rich numba +RUN git clone https://github.com/instructlab/training.git +RUN mkdir -p /ilab-data/training_output + +WORKDIR /training + +CMD ["/bin/bash"] diff --git a/chatbot/training/deepspeed/Makefile b/chatbot/training/deepspeed/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..83523ba861239613ca0d89fad9d7c4fa55e15128 --- /dev/null +++ b/chatbot/training/deepspeed/Makefile @@ -0,0 +1,19 @@ +IMAGE_NAME ?= deepspeed-trainer +CONTAINER_TOOL ?= podman +SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct) +BUILD_ARG_FILE ?= + +default: image + +.PHONY: image +image: + @mkdir -p ../build + rm -rf ../build/deepspeed-trainer + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + $(VENDOR:%=--build-arg VENDOR=%) \ + --file Containerfile \ + --squash-all \ + --tag oci:../build/deepspeed-trainer diff --git a/chatbot/training/ilab-wrapper/ilab b/chatbot/training/ilab-wrapper/ilab new file mode 100644 index 0000000000000000000000000000000000000000..ab4a4604f645e0556e5fad41dd5dc36dea9b4b8b --- /dev/null +++ b/chatbot/training/ilab-wrapper/ilab @@ -0,0 +1,103 @@ +#!/bin/bash + +echo-err() { echo "$@" >&2; } + +check_insights() { + if [[ -f /etc/insights-client/machine-id ]]; then + return + fi + if [[ -f /etc/ilab/insights-opt-out ]]; then + return + fi + local ID + eval "$(grep ^ID= /etc/os-release)" + if [[ "$ID" != "rhel" ]]; then + return + fi + cat << EOF +This host is not connected to Red Hat Insights. + +To connect this host to Red Hat Insights run the following command: +sudo rhc connect --organization --activation-key + +To generate an Activation Key: +https://console.redhat.com/insights/connector/activation-keys (this page will also display your Organization ID). + +For more information on Red Hat Insights, please visit: +https://docs.redhat.com/en/documentation/subscription_central/1-latest/html/getting_started_with_activation_keys_on_the_hybrid_cloud_console/assembly-creating-managing-activation-keys +EOF + exit 1 +} + +check_insights + +# Template values replaced by container build +CONTAINER_DEVICE="__REPLACE_CONTAINER_DEVICE__" +IMAGE_NAME="__REPLACE_IMAGE_NAME__" + +ENTRYPOINT="ilab" +PARAMS=("$@") + +if [[ -n "$ILAB_HOME" ]]; then + HOME="$ILAB_HOME" +fi + +for dir in "$HOME/.cache" "$HOME/.config" "$HOME/.local"; do + mkdir -p "$dir" +done + +if [[ "$1" = "shell" ]]; then + ENTRYPOINT=bash + PARAMS=() +fi + +# If you need to mount additional volumes into the container, you can specify them +# using the ILAB_ADDITIONAL_MOUNTS environment variable. +# +# Example ILAB_ADDITIONAL_MOUNTS usage: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path /host/path2:/container/path2" +# +# If your path contains spaces, you can use quotes: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path '/host/path with spaces':/container/path" +ADDITIONAL_MOUNTS=() +if [ -n "${ILAB_ADDITIONAL_MOUNTS}" ]; then + # (eval is used here to allow the user to specify mounts that might have spaces in them) + eval "ADDITIONAL_MOUNTS=(${ILAB_ADDITIONAL_MOUNTS})" +fi +ADDITIONAL_MOUNT_OPTIONS=() +for PODMAN_MOUNT in "${ADDITIONAL_MOUNTS[@]}"; do + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$PODMAN_MOUNT") +done + +# Add pull-secret to additional mounts +# In case of normal user, /run/user is used (XDG_RUNTIME_DIR), if root, it will be /run/containers +for authfile in \ + "${XDG_RUNTIME_DIR}/containers/auth.json" \ + /run/user/${UID}/containers/auth.json \ + /run/containers/${UID}/auth.json +do + if [[ -f "$authfile" ]]; then + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$authfile:/run/containers/0/auth.json") + break + fi +done + +PODMAN_COMMAND=("podman" "run" "--rm" "-it" + "--device" "${CONTAINER_DEVICE}" + "--security-opt" "label=disable" "--net" "host" + "--shm-size" "10G" + "--pids-limit" "-1" + "-v" "$HOME:$HOME" + "${ADDITIONAL_MOUNT_OPTIONS[@]}" + "--env" "VLLM_LOGGING_LEVEL" + "--env" "HOME" + "--env" "NCCL_DEBUG" + "--entrypoint" "$ENTRYPOINT" + "--env" "HF_TOKEN" + "--env" "SSL_CERT_FILE" + "--env" "SSL_CERT_DIR" + "${IMAGE_NAME}") + +exec "${PODMAN_COMMAND[@]}" "${PARAMS[@]}" diff --git a/chatbot/training/ilab-wrapper/ilab-qlora b/chatbot/training/ilab-wrapper/ilab-qlora new file mode 100644 index 0000000000000000000000000000000000000000..5670c1171deddbf6bb7f34c1b6360c2a32e6dc90 --- /dev/null +++ b/chatbot/training/ilab-wrapper/ilab-qlora @@ -0,0 +1,47 @@ +#!/bin/bash + +# Template values replaced by container build +TRAIN_DEVICE="__REPLACE_TRAIN_DEVICE__" +CONTAINER_DEVICE="__REPLACE_CONTAINER_DEVICE__" +CONTAINER_NAME="__REPLACE_CONTAINER_NAME__" + +# HF caching uses relative symlink structures, so keep cache relative to +# the central working directory +CONTAINER_CACHE="/instructlab/cache" +HOST_CACHE="$(pwd)/cache" +WORKDIR="$(pwd)" + +has_argument() { + match=$1 + shift + for arg in "$@"; do + if [[ "$arg" == *"$match"* ]]; then + return 0 + fi + done + return 1 +} + +mkdir -p "${HOST_CACHE}" +PODMAN_COMMAND=("podman" "run" "--rm" "-it" "--device" "${CONTAINER_DEVICE}" \ + "--security-opt" "label=disable" "--net" "host" \ + "-v" "${WORKDIR}:/instructlab" "--entrypoint" "" \ + "-e" "HF_HOME=${CONTAINER_CACHE}" \ + "${CONTAINER_NAME}") +if [[ "$1" = "init" ]]; then + if ! has_argument "--repository" "$@"; then + shift + "${PODMAN_COMMAND[@]}" ilab init \ + --repository https://github.com/instructlab/taxonomy.git "$@" + exit $? + fi +elif [[ "$1" = "train" ]]; then + if ! has_argument "--device" "$@"; then + shift + "${PODMAN_COMMAND[@]}" ilab train --device ${TRAIN_DEVICE} "$@" + exit $? + fi +fi + +"${PODMAN_COMMAND[@]}" ilab "$@" + diff --git a/chatbot/training/ilab-wrapper/ilab-training-launcher b/chatbot/training/ilab-wrapper/ilab-training-launcher new file mode 100644 index 0000000000000000000000000000000000000000..2c142629b58089442463294ff42d72e5b06d9bb2 --- /dev/null +++ b/chatbot/training/ilab-wrapper/ilab-training-launcher @@ -0,0 +1,86 @@ +#!/bin/bash + +if [[ $# -lt 6 ]]; then + echo "error: this is an internal command and not intented for direct execution, instead use ilab" + exit 1 +fi + +NPROC_PER_NODE="$1" +EFFECTIVE_BATCH_SIZE="$2" +TRAIN_DEVICE="$3" +SAMPLE_SIZE="$4" +NUM_EPOCHS="$5" +CONTAINER_DEVICE="$6" +CONTAINER_NAME="$7" +SDG_OUTPUT_PATH="$(pwd)" + +SAVE_SAMPLES=$(($SAMPLE_SIZE - 1)) +TESTING_DATA_PATH="/instructlab/generated" +TRAINING_DATA_PATH="/instructlab/generated" +DATASET_NAME="ilab-generated" +CONTAINER_CACHE="/instructlab/cache" +WORKDIR="$(pwd)" + +PODMAN_COMMAND=("podman" "run" "--device" "${CONTAINER_DEVICE}" \ + "--security-opt" "label=disable" \ + "--entrypoint" "" \ + "-v" "${SDG_OUTPUT_PATH}":/instructlab \ + "${CONTAINER_NAME}") +# Convert ilab generate output to match SDG output format for train and test data +mkdir -p ${SDG_OUTPUT_PATH}/training +"${PODMAN_COMMAND[@]}" bash -c "python /training/ilab_to_sdg.py \"${TRAINING_DATA_PATH}\" train \"${DATASET_NAME}\"; mv sdg_out.jsonl /instructlab/training/train.jsonl" +"${PODMAN_COMMAND[@]}" bash -c "python /training/ilab_to_sdg.py \"${TESTING_DATA_PATH}\" test \"${DATASET_NAME}\"; mv sdg_out.jsonl /instructlab/training/test.jsonl" + +# Add curated subset of taxonomy +"${PODMAN_COMMAND[@]}" bash -c "cat /training/sample-data/train_all_pruned_SDG.jsonl >> /instructlab/training/train.jsonl" + +# Pre-process generated data before training +"${PODMAN_COMMAND[@]}" bash -c \ +"python data_process.py --logging_level INFO \ +--data_path /instructlab/training/train.jsonl \ +--data_output_path=/instructlab/training \ +--max_seq_len 4096 \ +--model_name_or_path /instructlab/models/ibm/granite-7b-base" + +PODMAN_COMMAND=("podman" "run" "--rm" "-it" "--device" "${CONTAINER_DEVICE}" \ + "--shm-size=10g" "--security-opt" "label=disable" "--net" "host" \ + "-v" "${WORKDIR}:/instructlab" "--entrypoint" "" \ + "-e" "HF_HOME=${CONTAINER_CACHE}" \ + "${CONTAINER_NAME}") +mkdir -p training_output +# Run training +"${PODMAN_COMMAND[@]}" \ +torchrun \ +--nnodes 1 \ +--node_rank 0 \ +--nproc_per_node ${NPROC_PER_NODE} \ +--rdzv_id 101 \ +--rdzv_endpoint 0.0.0.0:8888 /training/main_ds.py \ +--model_name_or_path /instructlab/models/ibm/granite-7b-base \ +--data_path /instructlab/training/data.jsonl \ +--output_dir="/instructlab/training_output" \ +--num_epochs=${NUM_EPOCHS} \ +--effective_batch_size=${EFFECTIVE_BATCH_SIZE} \ +--learning_rate=2e-5 \ +--num_warmup_steps=385 \ +--save_samples=${SAVE_SAMPLES} \ +--log_level="INFO" \ +--sharding_strategy='HYBRID_SHARD' \ +--seed=19347 | tee training_output/0.log + +echo +echo + +if [[ -d "${SDG_OUTPUT_PATH}/training_output/hf_format" ]]; then + month=$(date +'%m') + day=$(date +'%d') + hour=$(date +'%H') + min=$(date +'%M') + + dest=${SDG_OUTPUT_PATH}/models/tuned-${month}${day}-${hour}${min} + mv training_output/hf_format "${dest}" + echo "Generated model in ${dest}:" + (cd ${dest}; find . -type d) +else + echo "Warning: No results were written!" +fi diff --git a/chatbot/training/instructlab/Makefile b/chatbot/training/instructlab/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..d45c8e8beb4dbefbff02ad0950b1d2942410fc20 --- /dev/null +++ b/chatbot/training/instructlab/Makefile @@ -0,0 +1,99 @@ +CONTAINER_TOOL ?= podman + +MAKEFLAGS += -j2 + +help: + @echo "Build a instructlab container image for specified vendor " + @echo + @echo " - make amd" + @echo " - make intel" + @echo " - make nvidia" + @echo " - make nvidia-quay" + @echo " - make amd-quay" + @echo " - make intel-quay" + +default: help + +.PHONY: all +all: nvidia intel amd + +INSTRUCTLAB_GIT_REPO ?= https://github.com/instructlab/instructlab.git +INSTRUCTLAB_GIT_BRANCH ?= main + +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +IMAGE_TAG ?= latest +INSTRUCTLAB_IMAGE_AMD ?= ${REGISTRY}/${REGISTRY_ORG}/instructlab-amd:${IMAGE_TAG} +INSTRUCTLAB_IMAGE_INTEL ?= ${REGISTRY}/${REGISTRY_ORG}/instructlab-intel:${IMAGE_TAG} +INSTRUCTLAB_IMAGE_NVIDIA ?= ${REGISTRY}/${REGISTRY_ORG}/instructlab-nvidia:${IMAGE_TAG} + + +./instructlab: + @mkdir -p ../build + git clone $(INSTRUCTLAB_GIT_REPO) instructlab 2> /dev/null || true + (cd instructlab; git pull origin $(INSTRUCTLAB_GIT_BRANCH)) + + +../build/instructlab-nvidia/oci-layout: ./instructlab + rm -rf ../build/instructlab-nvidia/ + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t oci:../build/instructlab-nvidia \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab/containers/cuda + +.PHONY: nvidia +nvidia: ../build/instructlab-nvidia/oci-layout + +../build/instructlab-amd/oci-layout: ./instructlab + rm -rf ../build/instructlab-amd/ + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t oci:../build/instructlab-amd \ + -f instructlab/containers/rocm/Containerfile \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab + +.PHONY: amd +amd: ../build/instructlab-amd/oci-layout + +../build/instructlab-intel/oci-layout: ./instructlab + rm -rf ../build/instructlab-intel/ + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t oci:../build/instructlab-intel \ + -f instructlab/containers/hpu/Containerfile \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab + +.PHONY: intel +intel: ../build/instructlab-intel/oci-layout + +.PHONY: nvidia-quay +nvidia-quay: instructlab + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t ${INSTRUCTLAB_IMAGE_NVIDIA} \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab/containers/cuda + "${CONTAINER_TOOL}" push ${INSTRUCTLAB_IMAGE_NVIDIA} + +.PHONY: amd-quay +amd-quay: instructlab + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t ${INSTRUCTLAB_IMAGE_AMD} \ + -f instructlab/containers/rocm/Containerfile \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab + "${CONTAINER_TOOL}" push ${INSTRUCTLAB_IMAGE_AMD} + +.PHONY: intel-quay +intel-quay: instructlab + "${CONTAINER_TOOL}" build \ + --squash-all \ + -t ${INSTRUCTLAB_IMAGE_INTEL} \ + -f instructlab/containers/hpu/Containerfile \ + $(VENDOR:%=--build-arg VENDOR=%) \ + instructlab + "${CONTAINER_TOOL}" push ${INSTRUCTLAB_IMAGE_INTEL} diff --git a/chatbot/training/intel-bootc/Containerfile b/chatbot/training/intel-bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..c09602b220dca66187e2b43436e0d4f611095f20 --- /dev/null +++ b/chatbot/training/intel-bootc/Containerfile @@ -0,0 +1,182 @@ +ARG DRIVER_TOOLKIT_IMAGE="quay.io/ai-lab/intel-builder:latest" +ARG BASEIMAGE="quay.io/centos-bootc/centos-bootc:stream9" + +ARG REPOS_REPO +ARG DRIVER_VERSION=1.17.1-40 +ARG HABANA_REPO="https://vault.habana.ai/artifactory/rhel/9/9.4" + +FROM ${DRIVER_TOOLKIT_IMAGE} as builder +ARG DRIVER_VERSION +ARG HABANA_REPO +# SHAs taken from original Makefile at HL packages +ARG DRIVER_GIT_SHA=78932ae +ARG NIC_GIT_SHA=31d590f + +WORKDIR /home/builder + +RUN . /etc/os-release \ + && export KERNEL_VERSION=$(rpm -q --qf '%{VERSION}-%{RELEASE}' kernel-core) \ + && export TARGET_ARCH=$(rpm -q --qf '%{ARCH}' kernel-core) \ + && export MAKEFLAGS="-j$(nproc)" \ + && rpm2cpio ${HABANA_REPO}/habanalabs-${DRIVER_VERSION}.el9.noarch.rpm | cpio -idmv \ + && pushd usr/src/habanalabs-${DRIVER_VERSION} \ + && MAKE_IB=1 make -f Makefile.nic KVERSION=${KERNEL_VERSION}.${TARGET_ARCH} GIT_SHA=${DRIVER_GIT_SHA} NIC_KMD_GIT_SHA=${NIC_GIT_SHA} \ + && make -f Makefile KVERSION=${KERNEL_VERSION}.${TARGET_ARCH} \ + && pushd drivers/infiniband/hw/hbl \ + && make KVERSION=${KERNEL_VERSION}.${TARGET_ARCH} + +# Build libraries +FROM ${DRIVER_TOOLKIT_IMAGE} as libbuilder +ARG DRIVER_VERSION +ARG HABANA_REPO +ARG ARTIFACTORY_URL="vault.habana.ai" + +USER root +COPY --chmod=755 scripts/os_dependencies.sh /tmp/ +RUN --mount=type=secret,id=extra-secrets-intel-bootc/BUILDERS_TOKEN /tmp/os_dependencies.sh \ + && mv /etc/selinux /etc/selinux.tmp \ + && dnf install -y --nodocs --allowerasing --best \ + git \ + make \ + gcc-c++ \ + unzip \ + habanalabs-graph-${DRIVER_VERSION}.el9 rdma-core-devel \ + && dnf clean all && rm -rf /var/cache/yum \ + && mv /etc/selinux.tmp /etc/selinux +ENV LIBFABRIC_VERSION="1.20.0" +ENV LIBFABRIC_ROOT="/opt/habanalabs/libfabric-${LIBFABRIC_VERSION}" +ENV LD_LIBRARY_PATH=$LIBFABRIC_ROOT/lib:/usr/lib/habanalabs:$LD_LIBRARY_PATH +ENV PATH=${LIBFABRIC_ROOT}/bin:$PATH +ENV RDMAV_FORK_SAFE=1 +ENV PIP_NO_CACHE_DIR=on +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +ENV RDMA_CORE_ROOT=/opt/habanalabs/rdma-core/src +ENV RDMA_CORE_LIB=${RDMA_CORE_ROOT}/build/lib + +RUN curl -L -o /tmp/libfabric-${LIBFABRIC_VERSION}.tar.bz2 https://github.com/ofiwg/libfabric/releases/download/v${LIBFABRIC_VERSION}/libfabric-${LIBFABRIC_VERSION}.tar.bz2 && \ + cd /tmp/ && tar --no-same-owner -xf /tmp/libfabric-${LIBFABRIC_VERSION}.tar.bz2 && \ + cd /tmp/libfabric-${LIBFABRIC_VERSION} && \ + ./configure --prefix=$LIBFABRIC_ROOT --enable-psm3-verbs --enable-verbs=yes --with-synapseai=/usr && \ + make -j$(nproc) && make install && cd / && rm -rf /tmp/libfabric-${LIBFABRIC_VERSION}.tar.bz2 /tmp/libfabric-${LIBFABRIC_VERSION} +#Hccl wrapper +RUN curl -L -o /tmp/main.zip https://github.com/HabanaAI/hccl_ofi_wrapper/archive/refs/heads/main.zip && \ + unzip /tmp/main.zip -d /tmp && \ + cd /tmp/hccl_ofi_wrapper-main && \ + make && cp -f libhccl_ofi_wrapper.so /usr/lib/habanalabs/libhccl_ofi_wrapper.so && \ + cd / && \ + rm -rf /tmp/main.zip /tmp/hccl_ofi_wrapper-main + +FROM ${BASEIMAGE} +ARG DRIVER_VERSION="1.17.1-40" +ARG ARTIFACTORY_URL="vault.habana.ai" + +USER root +COPY --from=builder /home/builder/usr/src/habanalabs-${DRIVER_VERSION}/drivers/accel/habanalabs/habanalabs.ko /tmp/extra/habanalabs.ko +COPY --from=builder /home/builder/usr/src/habanalabs-${DRIVER_VERSION}/drivers/infiniband/hw/hbl/habanalabs_ib.ko /tmp/extra/habanalabs_ib.ko +COPY --from=builder /home/builder/usr/src/habanalabs-${DRIVER_VERSION}/drivers/net/ethernet/intel/hbl_cn/habanalabs_cn.ko /tmp/extra/habanalabs_cn.ko +COPY --from=builder /home/builder/usr/src/habanalabs-${DRIVER_VERSION}/drivers/net/ethernet/intel/hbl_en/habanalabs_en.ko /tmp/extra/habanalabs_en.ko +COPY --from=builder /home/builder/etc/ /etc/ +COPY --from=builder /home/builder/lib/firmware/habanalabs/gaudi/ /lib/firmware/habanalabs/gaudi/ +COPY --from=builder /home/builder/usr/sbin /usr/sbin/ +COPY --from=libbuilder /usr/lib/habanalabs/libhccl_ofi_wrapper.so /usr/lib/habanalabs/libhccl_ofi_wrapper.so +COPY --from=libbuilder /opt/habanalabs/libfabric-1.20.0 /opt/habanalabs/libfabric-1.20.0 +COPY --chmod=755 scripts/os_dependencies.sh /tmp/ + +#Install python3.11 and other build stuff +RUN --mount=type=secret,id=extra-secrets-intel-bootc/BUILDERS_TOKEN \ + mv /etc/selinux /etc/selinux.tmp \ + && dnf install -y \ + python3.11 \ + python3.11-pip \ + python3.11-devel \ + git \ + make \ + gcc-c++ \ + unzip \ + && dnf clean all && rm -rf /var/cache/yum \ + && /tmp/os_dependencies.sh \ + && mv /etc/selinux.tmp /etc/selinux + +#Build ninja-build +RUN git clone https://github.com/ninja-build/ninja.git \ + && cd ninja \ + && ./configure.py --bootstrap \ + && rm -rf ninja + +RUN . /etc/os-release \ + && export OS_VERSION_MAJOR=$(echo ${VERSION} | cut -d'.' -f 1) \ + && export KERNEL_VERSION=$(rpm -q --qf '%{VERSION}-%{RELEASE}' kernel-core) \ + && export TARGET_ARCH=$(rpm -q --qf '%{ARCH}' kernel-core) \ + && mv /etc/selinux /etc/selinux.tmp \ + && dnf -y update --exclude=kernel* --exclude=microcode_ctl \ + && dnf install -y --nodocs --allowerasing --best \ + libogg-devel \ + libid3tag \ + opusfile-devel \ + sox-devel \ + libnl3-devel \ + habanalabs-rdma-core-${DRIVER_VERSION}.el9 \ + habanalabs-thunk-${DRIVER_VERSION}.el9 \ + habanalabs-firmware-${DRIVER_VERSION}.el9 \ + habanalabs-firmware-tools-${DRIVER_VERSION}.el9 \ + habanalabs-graph-${DRIVER_VERSION}.el9 \ + habanalabs-qual-${DRIVER_VERSION}.el9 \ + habanalabs-firmware-odm-${DRIVER_VERSION}.el9 \ + && rm -f /etc/yum.repos.d/habanalabs.repo && rm -f /etc/yum.repos.d/habana.repo \ + && dnf remove -y --noautoremove \ + python3.11-devel \ + && dnf clean all && rm -rf /var/cache/yum \ + && mv /etc/selinux.tmp /etc/selinux \ + && mv /tmp/extra /usr/lib/modules/${KERNEL_VERSION}.${TARGET_ARCH} \ + && echo "softdep habanalabs post: habanalabs_ib" > /etc/modprobe.d/habanalabs_ib_dep.conf \ + && depmod -a ${KERNEL_VERSION}.${TARGET_ARCH} \ + && rm -rf tmp/* + +RUN python3.11 -m pip install pip==23.3.1 setuptools==67.3.3 wheel==0.38.4 habana_media_loader=="1.17.1.40" + +RUN mv /etc/selinux /etc/selinux.tmp \ + && dnf install -y ${EXTRA_RPM_PACKAGES} \ + skopeo \ + cloud-init \ + rsync \ + && dnf clean all \ + && mv /etc/selinux.tmp /etc/selinux \ + && ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants + + +ARG SSHPUBKEY +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN if [ -n "${SSHPUBKEY}" ]; then \ + set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys; \ +fi +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +# Also make sure not to duplicate if a base image already has it specified. +RUN grep -q /usr/lib/containers/storage /etc/containers/storage.conf || \ + sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf +COPY duplicated/ilab-wrapper/ilab /usr/bin/ilab +RUN chmod +x /usr/bin/ilab +ARG INSTRUCTLAB_IMAGE="quay.io/ai-lab/intel-instructlab:latest" +ARG INSTRUCTLAB_IMAGE_PULL_SECRET="extra-secrets-intel-bootc" +RUN for i in /usr/bin/ilab*; do \ + sed -i 's/__REPLACE_TRAIN_DEVICE__/hpu/' $i; \ + sed -i "s%__REPLACE_IMAGE_NAME__%${INSTRUCTLAB_IMAGE}%" $i; \ + done + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers +# Prepull the instructlab image +RUN --mount=type=secret,id=${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson \ + if [ -f "/run/.input/instructlab-intel/oci-layout" ]; then \ + IID=$(podman --root /usr/lib/containers/storage pull oci:/run/.input/instructlab-intel) && \ + podman --root /usr/lib/containers/storage image tag ${IID} ${INSTRUCTLAB_IMAGE}; \ + elif [ -f "/run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson" ]; then \ + IID=$(sudo podman --root /usr/lib/containers/storage pull --authfile /run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson ${INSTRUCTLAB_IMAGE}); \ + else \ + IID=$(sudo podman --root /usr/lib/containers/storage pull ${INSTRUCTLAB_IMAGE}); \ + fi +RUN podman system reset --force 2>/dev/null diff --git a/chatbot/training/intel-bootc/Makefile b/chatbot/training/intel-bootc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..fc5b69abf094ee40c90077b2e66af8bd62e0b72b --- /dev/null +++ b/chatbot/training/intel-bootc/Makefile @@ -0,0 +1,25 @@ +IMAGE_NAME ?= intel-bootc + +include ../common/Makefile.common + +default: bootc + +.PHONY: bootc +bootc: prepare-files + ${CONTAINER_TOOL} build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(DRIVER_TOOLKIT_IMAGE:%=--build-arg DRIVER_TOOLKIT_IMAGE=%) \ + $(DRIVER_VERSION:%=--build-arg DRIVER_VERSION=%) \ + $(EXTRA_RPM_PACKAGES:%=--build-arg EXTRA_RPM_PACKAGES=%) \ + $(FROM:%=--build-arg BASEIMAGE=%) \ + $(INSTRUCTLAB_IMAGE:%=--build-arg INSTRUCTLAB_IMAGE=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + $(VENDOR:%=--build-arg VENDOR=%) \ + $(if $(SSH_PUBKEY),--build-arg SSHPUBKEY='$(SSH_PUBKEY)') \ + --cap-add SYS_ADMIN \ + --file Containerfile \ + --security-opt label=disable \ + --tag "${BOOTC_IMAGE}" \ + -v ${OUTDIR}:/run/.input:ro \ + ${CONTAINER_TOOL_EXTRA_ARGS} . diff --git a/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service b/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service new file mode 100644 index 0000000000000000000000000000000000000000..1e479959d09150e8f2e93a4e8cc84bd3b4b4968c --- /dev/null +++ b/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service @@ -0,0 +1,12 @@ +[Unit] +Description=Check for available RHEL AI upgrade +ConditionPathExists=/run/ostree-booted +After=network-online.target +StartLimitIntervalSec=400 +StartLimitBurst=3 + +[Service] +Type=oneshot +ExecStart=/usr/libexec/upgrade-informer +Restart=on-failure +RestartSec=90 diff --git a/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer b/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer new file mode 100644 index 0000000000000000000000000000000000000000..229db9faa6ee71810a62fbf4d6e33686bab562fd --- /dev/null +++ b/chatbot/training/intel-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Runs upgrade informer periodically +ConditionPathExists=/run/ostree-booted + +[Timer] +OnBootSec=1h +OnUnitInactiveSec=1day +RandomizedDelaySec=2h + +[Install] +WantedBy=timers.target diff --git a/chatbot/training/intel-bootc/duplicated/common/usr/libexec/upgrade-informer b/chatbot/training/intel-bootc/duplicated/common/usr/libexec/upgrade-informer new file mode 100644 index 0000000000000000000000000000000000000000..5f8979c78343509535036076e6ae191b70aad987 --- /dev/null +++ b/chatbot/training/intel-bootc/duplicated/common/usr/libexec/upgrade-informer @@ -0,0 +1,37 @@ +#!/bin/bash + +# Run the command and capture its output +output=$(bootc upgrade --check | sed -e 1q) +message_file="/etc/motd.d/upgrade-message" +bootc_auth="/etc/ostree/auth.json" + +if [[ $output == Update\ available* ]]; then + if [[ ! -f $message_file ]]; then + echo "New version was found" + bootc_image=$(awk '{print $4}' <<< "$output") + # If auth file exists we should use it + auth_params="" + if [[ -f $bootc_auth ]]; then + auth_params="--authfile $bootc_auth" + fi + + # Get image version + # shellcheck disable=SC2086 + image_version_id=$(skopeo inspect --format json $auth_params "$bootc_image" | jq -r '.Labels | .["image_version_id"] // empty') + + # If upgrade available, write the output to the file + cat > $message_file << EOF + +** Attention! ** +** A new $image_version_id version is available ** +** In order to apply it run: bootc upgrade --apply +** Please note that the system will reboot after the upgrade ** + +EOF + fi +else + echo "No upgrade was found" + rm $message_file 2> /dev/null +fi + +echo "Finished running upgrade informer" diff --git a/chatbot/training/intel-bootc/duplicated/ilab-wrapper/ilab b/chatbot/training/intel-bootc/duplicated/ilab-wrapper/ilab new file mode 100644 index 0000000000000000000000000000000000000000..26ac8634dcb70d34cd3c5ab68c4062d9ce60e383 --- /dev/null +++ b/chatbot/training/intel-bootc/duplicated/ilab-wrapper/ilab @@ -0,0 +1,145 @@ +#!/bin/bash + +echo-err() { echo "$@" >&2; } + +verify_range() { + subuid_range="$1" + username="$2" + NUMBER_OF_MATCHING_SUBUID_RANGES=$(if [[ -z "$subuid_range" ]]; then echo 0; else wc -l <<<"$subuid_range"; fi) + + if [[ "$NUMBER_OF_MATCHING_SUBUID_RANGES" == 0 ]]; then + echo-err "No /etc/subuid range found for user $username ($UID)" + exit 1 + elif [[ "$NUMBER_OF_MATCHING_SUBUID_RANGES" != 1 ]]; then + # TODO: Handle multiple subuid ranges. But for now, hard fail + echo-err "Multiple /etc/subuid ranges found for user $username ($UID), this is currently unsupported:" + echo-err "$subuid_range" + exit 1 + fi +} + +check_insights() { + if [[ -f /etc/insights-client/machine-id ]]; then + return + fi + if [[ -f /etc/ilab/insights-opt-out ]]; then + return + fi + local ID + eval "$(grep ^ID= /etc/os-release)" + if [[ "$ID" != "rhel" ]]; then + return + fi + cat << EOF +This host is not connected to Red Hat Insights. + +To connect this host to Red Hat Insights run the following command: +sudo rhc connect --organization --activation-key + +To generate an Activation Key: +https://console.redhat.com/insights/connector/activation-keys (this page will also display your Organization ID). + +For more information on Red Hat Insights, please visit: +https://docs.redhat.com/en/documentation/subscription_central/1-latest/html/getting_started_with_activation_keys_on_the_hybrid_cloud_console/assembly-creating-managing-activation-keys +EOF + exit 1 +} + +check_insights + +# Template values replaced by container build +CONTAINER_DEVICE="__REPLACE_CONTAINER_DEVICE__" +IMAGE_NAME="__REPLACE_IMAGE_NAME__" + +ENTRYPOINT="ilab" +PARAMS=("$@") + +if [[ -n "$ILAB_HOME" ]]; then + HOME="$ILAB_HOME" +fi + +for dir in "$HOME/.cache" "$HOME/.config" "$HOME/.local"; do + mkdir -p "$dir" +done + +if [[ "$1" = "shell" ]]; then + ENTRYPOINT=bash + PARAMS=() +fi + +# If you need to mount additional volumes into the container, you can specify them +# using the ILAB_ADDITIONAL_MOUNTS environment variable. +# +# Example ILAB_ADDITIONAL_MOUNTS usage: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path /host/path2:/container/path2" +# +# If your path contains spaces, you can use quotes: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path '/host/path with spaces':/container/path" +ADDITIONAL_MOUNTS=() +if [ -n "${ILAB_ADDITIONAL_MOUNTS}" ]; then + # (eval is used here to allow the user to specify mounts that might have spaces in them) + eval "ADDITIONAL_MOUNTS=(${ILAB_ADDITIONAL_MOUNTS})" +fi +ADDITIONAL_MOUNT_OPTIONS=() +for PODMAN_MOUNT in "${ADDITIONAL_MOUNTS[@]}"; do + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$PODMAN_MOUNT") +done + +# Add pull-secret to additional mounts +# In case of normal user, /run/user is used (XDG_RUNTIME_DIR), if root, it will be /run/containers +for authfile in \ + "${XDG_RUNTIME_DIR}/containers/auth.json" \ + /run/user/${UID}/containers/auth.json \ + /run/containers/${UID}/auth.json +do + if [[ -f "$authfile" ]]; then + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$authfile:/run/containers/0/auth.json") + break + fi +done + +# We run the container as sudo in order to be able to access the root container +# storage, which has the ilab image pre-pulled. But for security reasons we map +# root UID 0 inside the container to the current user's UID (and all the other +# subuids to the user's /etc/subuid range) so that we're effectively running +# the container as the current user. +# +# In the future, we will run podman as the current user, once we figure a +# reasonable way for the current user to access the root's user container +# storage. +if [[ "$UID" == 0 ]]; then + # If we're already running as root, we don't need to map any UIDs + IMPERSONATE_CURRENT_USER_PODMAN_FLAGS=() +else + CURRENT_USER_NAME=$(id --user --name) + CURRENT_USER_SUBUID_RANGE=$(awk \ + --field-separator ':' \ + --assign current_user="$CURRENT_USER_NAME" \ + --assign current_uid="$UID" \ + '$1 == current_user || $1 == current_uid {print $2 ":" $3}' \ + /etc/subuid) + + verify_range "$CURRENT_USER_SUBUID_RANGE" "$CURRENT_USER_NAME" + + IMPERSONATE_CURRENT_USER_PODMAN_FLAGS=("--uidmap" "0:$UID" "--uidmap" "1:$CURRENT_USER_SUBUID_RANGE") +fi + +PRESERVE_ENV="VLLM_LOGGING_LEVEL,NCCL_DEBUG,HOME,HF_TOKEN" +PODMAN_COMMAND=("sudo" "--preserve-env=$PRESERVE_ENV" "podman" "run" "--rm" "-it" + "${IMPERSONATE_CURRENT_USER_PODMAN_FLAGS[@]}" + "--device" "${CONTAINER_DEVICE}" + "--security-opt" "label=disable" "--net" "host" + "--shm-size" "10G" + "--pids-limit" "-1" + "-v" "$HOME:$HOME" + "${ADDITIONAL_MOUNT_OPTIONS[@]}" + "--env" "VLLM_LOGGING_LEVEL" + "--env" "HOME" + "--env" "NCCL_DEBUG" + "--entrypoint" "$ENTRYPOINT" + "--env" "HF_TOKEN" + "${IMAGE_NAME}") + +exec "${PODMAN_COMMAND[@]}" "${PARAMS[@]}" diff --git a/chatbot/training/intel-bootc/scripts/os_dependencies.sh b/chatbot/training/intel-bootc/scripts/os_dependencies.sh new file mode 100644 index 0000000000000000000000000000000000000000..74aefd3a2befa9e95e4128d3e0690534739ee31c --- /dev/null +++ b/chatbot/training/intel-bootc/scripts/os_dependencies.sh @@ -0,0 +1,35 @@ +#!/bin/bash +REPOS_REPO="redhat/rhel-ai/wheels/builder.git" + +centos_habana_repos() { +echo "[habanalabs]" > /etc/yum.repos.d/habanalabs.repo && \ +echo "name=Habana RH9 Linux repo" >> /etc/yum.repos.d/habanalabs.repo && \ +echo "baseurl=https://${ARTIFACTORY_URL}/artifactory/rhel/9/9.4" >> /etc/yum.repos.d/habanalabs.repo && \ +echo "gpgkey=https://${ARTIFACTORY_URL}/artifactory/api/v2/repositories/rhel/keyPairs/primary/public" >> /etc/yum.repos.d/habanalabs.repo && \ +echo "gpgcheck=1" >> /etc/yum.repos.d/habanalabs.repo && \ +update-crypto-policies --set DEFAULT:SHA1 +} +centos_epel_crb() { +#EPEL only needed in CentOS for libsox-devel +dnf config-manager --set-enabled crb && \ +dnf install -y https://dl.fedoraproject.org/pub/epel/epel{,-next}-release-latest-9.noarch.rpm +} +OS=$(grep -w ID /etc/os-release) + +echo "OS line is $OS" +if [[ "$OS" == *"rhel"* ]]; then \ + mkdir -p /tmp/git && cd /tmp/git && \ + GIT_TOKEN=$(cat /run/secrets/extra-secrets-intel-bootc/BUILDERS_TOKEN) && \ + git clone https://dummy_user:${GIT_TOKEN}@gitlab.com/${REPOS_REPO} && \ + cd builder/repos && \ + cp redhat.repo rhelai.repo habanalabs.repo /etc/yum.repos.d/ && \ + cp RPM-GPG-KEY-HABANALABS /etc/pki/rpm-gpg/ && \ + dnf config-manager --enable habanalabs && \ + dnf config-manager --enable rhelai-1.2-stage && \ + rm -rf /tmp/git; +elif [[ "$OS" == *"centos"* ]]; then \ + centos_habana_repos && centos_epel_crb; \ +else + echo "Only RHEL and CentOS supported." +fi + diff --git a/chatbot/training/model/Containerfile b/chatbot/training/model/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..0235d71cd33aeb282ee7f190ceb493d7807f6030 --- /dev/null +++ b/chatbot/training/model/Containerfile @@ -0,0 +1,10 @@ +FROM registry.access.redhat.com/ubi9/ubi + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +RUN dnf install -y python3-pip && python3 -m pip install huggingface_hub[cli] +COPY entrypoint.sh /entrypoint.sh +WORKDIR /download +ENTRYPOINT ["bash", "/entrypoint.sh"] diff --git a/chatbot/training/model/Makefile b/chatbot/training/model/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3d7885df3003ecbc823398a70fd23081bd552f9f --- /dev/null +++ b/chatbot/training/model/Makefile @@ -0,0 +1,57 @@ +include ../common/Makefile.common + +TARGET_MODELS_IMAGE ?= $(BOOTC_MODELS_IMAGE) +FROM_BOOTC_IMAGE ?= $(BOOTC_IMAGE) + +GRANITE_MODEL_REPO ?= instructlab/granite-7b-lab + +HF_TOKEN ?= $(shell echo $$HF_TOKEN) + +BUILD_MODELS_PATH := $(shell realpath ..)/build/models +COMMON_PATH := $(shell realpath ../common) + +MODEL_REPO ?= + +default: download + +.PHONY: image +image: + "${CONTAINER_TOOL}" build \ + --file Containerfile \ + --tag $(REGISTRY)/$(REGISTRY_ORG)/model-downloader:latest \ + ${CONTAINER_TOOL_EXTRA_ARGS} . + +.PHONY: download-all +download-all: image + $(MAKE) MODEL_REPO=$(GRANITE_MODEL_REPO) download-model + +.PHONY: download-model +download-model: + mkdir -p $(BUILD_MODELS_PATH) + podman run \ + -e HF_TOKEN=$(HF_TOKEN) \ + -v $(BUILD_MODELS_PATH):/download:z \ + --pull=never \ + -e MODEL_REPO=$(MODEL_REPO) \ + -t $(REGISTRY)/$(REGISTRY_ORG)/model-downloader:latest + +.PHONY: generate-model-cfile +generate-model-cfile: download-all + echo "FROM ${FROM_BOOTC_IMAGE}" > ${MODELS_CONTAINERFILE} + echo "RUN rsync -ah --progress --exclude '.hug*' --exclude '*.safetensors' /run/.input/models /usr/share" >> ${MODELS_CONTAINERFILE} + "${CONTAINER_TOOL}" run \ + -v .:/work:z \ + -v ${OUTDIR}:/run/.input:ro \ + --pull=never \ + --entrypoint python3 \ + $(REGISTRY)/$(REGISTRY_ORG)/model-downloader:latest \ + /work/generate-model-cfile.py /run/.input/models >> ${MODELS_CONTAINERFILE} + +.PHONY: bootc-models +bootc-models: generate-model-cfile + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + --file ${MODELS_CONTAINERFILE} \ + --security-opt label=disable \ + --tag "${TARGET_MODELS_IMAGE}" \ + -v ${OUTDIR}:/run/.input:ro diff --git a/chatbot/training/model/entrypoint.sh b/chatbot/training/model/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..fe8541487bf11582c0d0cbd43a65ef0f006fe523 --- /dev/null +++ b/chatbot/training/model/entrypoint.sh @@ -0,0 +1,7 @@ +set -x +if [ -z "$HF_TOKEN" ]; then + echo "Error. Please set your \$HF_TOKEN in env. Required to pull mixtral." + exit 1 +fi + +huggingface-cli download --exclude "*.pt" --local-dir "/download/${MODEL_REPO}" "${MODEL_REPO}" diff --git a/chatbot/training/model/generate-model-cfile.py b/chatbot/training/model/generate-model-cfile.py new file mode 100644 index 0000000000000000000000000000000000000000..8591aed0263ffd13a542141f7ad604ed5e21f454 --- /dev/null +++ b/chatbot/training/model/generate-model-cfile.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import os +import sys + +def isHuggingDir(elements): + for n in s: + if n.startswith(".hug"): + return True + return False + +def printNonEmpty(*args): + if len(args[0]) > 0: + print(*args) + +dir = sys.argv[1] +cwd = os.getcwd() +os.chdir(dir) +c = 0 +result="" +for root, dirs, files in os.walk("."): + s = root.split(os.path.sep) + s.pop(0) + if isHuggingDir(s): + continue + for file in files: + if not file.endswith(".safetensors"): + continue + path = os.path.join("/run", ".input", "models", *s, file) + partial = os.path.join("/usr", "share", "models", *s, file) + cmd = f"rsync -ah --progress {path} {partial}" + if c % 4 != 0: + result = f"{result} \\\n\t && {cmd}" + else: + printNonEmpty(result) + result = f"RUN {cmd}" + c += 1 +printNonEmpty(result) +os.chdir(cwd) diff --git a/chatbot/training/nvidia-bootc/Containerfile b/chatbot/training/nvidia-bootc/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..48a461bb9a4a1f976b89b595e590ceb8f0e8b56f --- /dev/null +++ b/chatbot/training/nvidia-bootc/Containerfile @@ -0,0 +1,202 @@ +ARG DRIVER_TOOLKIT_IMAGE="quay.io/ai-lab/nvidia-builder:latest" +ARG BASEIMAGE="quay.io/centos-bootc/centos-bootc:stream9" +ARG INSTRUCTLAB_IMAGE="quay.io/ai-lab/instructlab-nvidia:latest" + +FROM ${DRIVER_TOOLKIT_IMAGE} as builder + +ARG BASE_URL='https://us.download.nvidia.com/tesla' + +ARG BUILD_ARCH='' +ARG TARGET_ARCH='' + +ARG DRIVER_VERSION='550.90.07' + +ARG VENDOR='' +ARG RPM_HOST='' + +USER builder + +WORKDIR /home/builder +COPY --chown=1001:0 x509-configuration.ini x509-configuration.ini + +RUN export KVER=$(rpm -q --qf "%{VERSION}" kernel-core) \ + KREL=$(rpm -q --qf "%{RELEASE}" kernel-core | sed 's/\.el.\(_.\)*$//') \ + KDIST=$(rpm -q --qf "%{RELEASE}" kernel-core | awk -F '.' '{ print "."$NF}') \ + OS_VERSION_MAJOR=$(grep "^VERSION=" /etc/os-release | cut -d '=' -f 2 | sed 's/"//g' | cut -d '.' -f 1) \ + && if [ "${BUILD_ARCH}" == "" ]; then \ + export BUILD_ARCH=$(arch) \ + && export TARGET_ARCH=$(echo "${BUILD_ARCH}" | sed 's/+64k//') ;\ + fi \ + && DRIVER_STREAM=$(echo ${DRIVER_VERSION} | cut -d '.' -f 1) \ + && git clone --depth 1 --single-branch -b rhel${OS_VERSION_MAJOR} https://github.com/NVIDIA/yum-packaging-precompiled-kmod \ + && cd yum-packaging-precompiled-kmod \ + && mkdir BUILD BUILDROOT RPMS SRPMS SOURCES SPECS \ + && mkdir nvidia-kmod-${DRIVER_VERSION}-${BUILD_ARCH} \ + && curl -sLOf ${BASE_URL}/${DRIVER_VERSION}/NVIDIA-Linux-${TARGET_ARCH}-${DRIVER_VERSION}.run \ + && sh ./NVIDIA-Linux-${TARGET_ARCH}-${DRIVER_VERSION}.run --extract-only --target tmp \ + && mv tmp/kernel-open nvidia-kmod-${DRIVER_VERSION}-${BUILD_ARCH}/kernel \ + && tar -cJf SOURCES/nvidia-kmod-${DRIVER_VERSION}-${BUILD_ARCH}.tar.xz nvidia-kmod-${DRIVER_VERSION}-${BUILD_ARCH} \ + && mv kmod-nvidia.spec SPECS/ \ + && openssl req -x509 -new -nodes -utf8 -sha256 -days 36500 -batch \ + -config ${HOME}/x509-configuration.ini \ + -outform DER -out SOURCES/public_key.der \ + -keyout SOURCES/private_key.priv \ + && rpmbuild \ + --define "% _arch ${BUILD_ARCH}" \ + --define "%_topdir $(pwd)" \ + --define "debug_package %{nil}" \ + --define "kernel ${KVER}" \ + --define "kernel_release ${KREL}" \ + --define "kernel_dist ${KDIST}" \ + --define "driver ${DRIVER_VERSION}" \ + --define "driver_branch ${DRIVER_STREAM}" \ + --define "vendor ${VENDOR:-undefined}" \ + --define "_buildhost ${RPM_HOST:-${HOSTNAME}}" \ + -v -bb SPECS/kmod-nvidia.spec + +FROM ${BASEIMAGE} + +ARG BASE_URL='https://us.download.nvidia.com/tesla' + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +ARG DRIVER_TYPE=passthrough +ENV NVIDIA_DRIVER_TYPE=${DRIVER_TYPE} + +ARG DRIVER_VERSION='550.90.07' +ENV NVIDIA_DRIVER_VERSION=${DRIVER_VERSION} +ARG CUDA_VERSION='12.4.1' + +ARG TARGET_ARCH='' +ENV TARGETARCH=${TARGET_ARCH} + +ARG EXTRA_RPM_PACKAGES='' + +# Disable vGPU version compatibility check by default +ARG DISABLE_VGPU_VERSION_CHECK=true +ENV DISABLE_VGPU_VERSION_CHECK=$DISABLE_VGPU_VERSION_CHECK + +USER root + +COPY --from=builder /home/builder/yum-packaging-precompiled-kmod/RPMS/*/*.rpm /rpms/ +# Temporary workaround until the permanent fix for libdnf is merged +COPY nvidia-toolkit-firstboot.service /usr/lib/systemd/system/nvidia-toolkit-firstboot.service +# Enable common services +COPY duplicated/common/usr /usr + +ARG IMAGE_VERSION_ID + +# TODO: rework this monstrosity into a build.sh (or even not shell script) +# The need for the `cp /etc/dnf/dnf.conf` is a workaround for https://github.com/containers/bootc/issues/637 +RUN mv /etc/selinux /etc/selinux.tmp \ + && dnf install -y /rpms/kmod-nvidia-*.rpm \ + && export OS_VERSION_MAJOR=$(grep "^VERSION=" /etc/os-release | cut -d '=' -f 2 | sed 's/"//g' | cut -d '.' -f 1) \ + && if [ "${TARGET_ARCH}" == "" ]; then \ + export TARGET_ARCH="$(arch)" ;\ + fi \ + && if [ "${TARGET_ARCH}" == "aarch64" ]; then CUDA_REPO_ARCH="sbsa"; fi \ + && export DRIVER_STREAM=$(echo ${DRIVER_VERSION} | cut -d '.' -f 1) \ + CUDA_VERSION_ARRAY=(${CUDA_VERSION//./ }) \ + CUDA_DASHED_VERSION=${CUDA_VERSION_ARRAY[0]}-${CUDA_VERSION_ARRAY[1]} \ + CUDA_REPO_ARCH=${TARGET_ARCH} \ + && cp -a /etc/dnf/dnf.conf{,.tmp} && mv /etc/dnf/dnf.conf{.tmp,} \ + && dnf config-manager --best --nodocs --setopt=install_weak_deps=False --save \ + && dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel${OS_VERSION_MAJOR}/${CUDA_REPO_ARCH}/cuda-rhel${OS_VERSION_MAJOR}.repo \ + && dnf -y module enable nvidia-driver:${DRIVER_STREAM}/default \ + && dnf install -y \ + cloud-init \ + git \ + git-lfs \ + pciutils \ + tmux \ + nvidia-driver-${DRIVER_VERSION} \ + nvidia-driver-cuda-${DRIVER_VERSION} \ + nvidia-driver-libs-${DRIVER_VERSION} \ + nvidia-driver-NVML-${DRIVER_VERSION} \ + cuda-compat-${CUDA_DASHED_VERSION} \ + cuda-cudart-${CUDA_DASHED_VERSION} \ + nvidia-persistenced-${DRIVER_VERSION} \ + nvidia-container-toolkit \ + rsync \ + skopeo \ + ${EXTRA_RPM_PACKAGES} \ + && if [[ "$(rpm -qa | grep kernel-core | wc -l)" != "1" ]]; then \ + echo "ERROR - Multiple kernel-core packages detected"; \ + echo "This usually means that nvidia-drivers are built for a different kernel version than the one installed"; \ + exit 1; \ + fi \ + && if [ "$DRIVER_TYPE" != "vgpu" ] && [ "$TARGET_ARCH" != "arm64" ]; then \ + versionArray=(${DRIVER_VERSION//./ }); \ + DRIVER_BRANCH=${versionArray[0]}; \ + dnf module enable -y nvidia-driver:${DRIVER_BRANCH} && \ + dnf install -y nvidia-fabric-manager-${DRIVER_VERSION} libnvidia-nscq-${DRIVER_BRANCH}-${DRIVER_VERSION} ; \ + fi \ + && . /etc/os-release && if [ "${ID}" == "rhel" ]; then \ + # Install rhc connect for insights telemetry gathering + dnf install -y rhc rhc-worker-playbook; \ + # Adding rhel ai identity to os-release file for insights usage + sed -i -e "/^VARIANT=/ {s/^VARIANT=.*/VARIANT=\"RHEL AI\"/; t}" -e "\$aVARIANT=\"RHEL AI\"" /usr/lib/os-release; \ + sed -i -e "/^VARIANT_ID=/ {s/^VARIANT_ID=.*/VARIANT_ID=rhel_ai/; t}" -e "\$aVARIANT_ID=rhel_ai" /usr/lib/os-release; \ + sed -i -e "/^RHEL_AI_VERSION_ID=/ {s/^RHEL_AI_VERSION_ID=.*/RHEL_AI_VERSION_ID='${IMAGE_VERSION_ID}'/; t}" -e "\$aRHEL_AI_VERSION_ID='${IMAGE_VERSION_ID}'" /usr/lib/os-release; \ + # disable auto upgrade service + rm -f /usr/lib/systemd/system/default.target.wants/bootc-fetch-apply-updates.timer; \ + fi \ + && dnf clean all \ + && ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants \ + && mv /etc/selinux.tmp /etc/selinux \ + && ln -s /usr/lib/systemd/system/nvidia-toolkit-firstboot.service /usr/lib/systemd/system/basic.target.wants/nvidia-toolkit-firstboot.service \ + && echo "blacklist nouveau" > /etc/modprobe.d/blacklist_nouveau.conf \ + && sed -i '/\[Unit\]/a ConditionDirectoryNotEmpty=/proc/driver/nvidia-nvswitch/devices' /usr/lib/systemd/system/nvidia-fabricmanager.service \ + && ln -s /usr/lib/systemd/system/nvidia-fabricmanager.service /etc/systemd/system/multi-user.target.wants/nvidia-fabricmanager.service \ + && ln -s /usr/lib/systemd/system/nvidia-persistenced.service /etc/systemd/system/multi-user.target.wants/nvidia-persistenced.service + +ARG SSHPUBKEY + +# The --build-arg "SSHPUBKEY=$(cat ~/.ssh/id_rsa.pub)" option inserts your +# public key into the image, allowing root access via ssh. +RUN if [ -n "${SSHPUBKEY}" ]; then \ + set -eu; mkdir -p /usr/ssh && \ + echo 'AuthorizedKeysFile /usr/ssh/%u.keys .ssh/authorized_keys .ssh/authorized_keys2' >> /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo ${SSHPUBKEY} > /usr/ssh/root.keys && chmod 0600 /usr/ssh/root.keys; \ +fi + +# Setup /usr/lib/containers/storage as an additional store for images. +# Remove once the base images have this set by default. +# Also make sure not to duplicate if a base image already has it specified. +RUN grep -q /usr/lib/containers/storage /etc/containers/storage.conf || \ + sed -i -e '/additionalimage.*/a "/usr/lib/containers/storage",' \ + /etc/containers/storage.conf + +COPY duplicated/ilab-wrapper/ilab /usr/bin/ilab +RUN chmod +x /usr/bin/ilab + +ARG INSTRUCTLAB_IMAGE="quay.io/ai-lab/instructlab-nvidia:latest" +ARG INSTRUCTLAB_IMAGE_PULL_SECRET="instructlab-nvidia-pull" + +RUN for i in /usr/bin/ilab*; do \ + sed -i 's/__REPLACE_TRAIN_DEVICE__/cuda/' $i; \ + sed -i 's/__REPLACE_CONTAINER_DEVICE__/nvidia.com\/gpu=all/' $i; \ + sed -i "s%__REPLACE_IMAGE_NAME__%${INSTRUCTLAB_IMAGE}%" $i; \ + done + +# Added for running as an OCI Container to prevent Overlay on Overlay issues. +VOLUME /var/lib/containers + +RUN --mount=type=secret,id=${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson \ + if [ -f "/run/.input/instructlab-nvidia/oci-layout" ]; then \ + IID=$(podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared pull oci:/run/.input/instructlab-nvidia) && \ + podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared image tag ${IID} ${INSTRUCTLAB_IMAGE}; \ + elif [ -f "/run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson" ]; then \ + IID=$(sudo podman --root /usr/lib/containers/storage pull --storage-opt overlay.force_mask=shared --authfile /run/secrets/${INSTRUCTLAB_IMAGE_PULL_SECRET}/.dockerconfigjson ${INSTRUCTLAB_IMAGE}); \ + else \ + IID=$(sudo podman --root /usr/lib/containers/storage --storage-opt overlay.force_mask=shared pull ${INSTRUCTLAB_IMAGE}); \ + fi && \ + chmod -R a+rX /usr/lib/containers + +COPY containers-storage.conf /etc/skel/.config/containers/storage.conf + +RUN podman system reset --force 2>/dev/null + +LABEL image_version_id="${IMAGE_VERSION_ID}" diff --git a/chatbot/training/nvidia-bootc/Makefile b/chatbot/training/nvidia-bootc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..28428467e1787a581d8d8547a79eab6cee5486fc --- /dev/null +++ b/chatbot/training/nvidia-bootc/Makefile @@ -0,0 +1,33 @@ +HARDWARE ?= nvidia +IMAGE_NAME ?= $(HARDWARE)-bootc + +CUDA_VERSION ?= +OS_VERSION_MAJOR ?= +include ../common/Makefile.common + +default: bootc + +.PHONY: bootc +bootc: driver-toolkit check-sshkey prepare-files + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(CUDA_VERSION:%=--build-arg CUDA_VERSION=%) \ + $(DRIVER_TOOLKIT_IMAGE:%=--build-arg DRIVER_TOOLKIT_IMAGE=%) \ + $(DRIVER_VERSION:%=--build-arg DRIVER_VERSION=%) \ + $(DRIVER_VERSION:%=--label driver-version=%) \ + $(IMAGE_VERSION_ID:%=--build-arg IMAGE_VERSION_ID=%) \ + $(EXTRA_RPM_PACKAGES:%=--build-arg EXTRA_RPM_PACKAGES=%) \ + $(FROM:%=--build-arg BASEIMAGE=%) \ + $(INSTRUCTLAB_IMAGE:%=--build-arg INSTRUCTLAB_IMAGE=%) \ + $(KERNEL_VERSION:%=--build-arg KERNEL_VERSION=%) \ + $(OS_VERSION_MAJOR:%=--build-arg OS_VERSION_MAJOR=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + $(VENDOR:%=--build-arg VENDOR=%) \ + $(if $(SSH_PUBKEY),--build-arg SSHPUBKEY='$(SSH_PUBKEY)') \ + --cap-add SYS_ADMIN \ + --file Containerfile \ + --security-opt label=disable \ + --tag "${BOOTC_IMAGE}" \ + -v ${OUTDIR}:/run/.input:ro \ + ${CONTAINER_TOOL_EXTRA_ARGS} . diff --git a/chatbot/training/nvidia-bootc/containers-storage.conf b/chatbot/training/nvidia-bootc/containers-storage.conf new file mode 100644 index 0000000000000000000000000000000000000000..00a70bc70f1339bed6077d5793c9792916607997 --- /dev/null +++ b/chatbot/training/nvidia-bootc/containers-storage.conf @@ -0,0 +1,17 @@ +[storage] +driver = "overlay" + +[storage.options] +size = "" +remap-uids = "" +remap-gids = "" +ignore_chown_errors = "" +remap-user = "" +remap-group = "" +skip_mount_home = "" +mount_program = "/usr/bin/fuse-overlayfs" +mountopt = "" +additionalimagestores = [ "/usr/lib/containers/storage",] + +[storage.options.overlay] +force_mask = "shared" diff --git a/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service b/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service new file mode 100644 index 0000000000000000000000000000000000000000..1e479959d09150e8f2e93a4e8cc84bd3b4b4968c --- /dev/null +++ b/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.service @@ -0,0 +1,12 @@ +[Unit] +Description=Check for available RHEL AI upgrade +ConditionPathExists=/run/ostree-booted +After=network-online.target +StartLimitIntervalSec=400 +StartLimitBurst=3 + +[Service] +Type=oneshot +ExecStart=/usr/libexec/upgrade-informer +Restart=on-failure +RestartSec=90 diff --git a/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer b/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer new file mode 100644 index 0000000000000000000000000000000000000000..229db9faa6ee71810a62fbf4d6e33686bab562fd --- /dev/null +++ b/chatbot/training/nvidia-bootc/duplicated/common/usr/lib/systemd/system/upgrade-informer.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Runs upgrade informer periodically +ConditionPathExists=/run/ostree-booted + +[Timer] +OnBootSec=1h +OnUnitInactiveSec=1day +RandomizedDelaySec=2h + +[Install] +WantedBy=timers.target diff --git a/chatbot/training/nvidia-bootc/duplicated/common/usr/libexec/upgrade-informer b/chatbot/training/nvidia-bootc/duplicated/common/usr/libexec/upgrade-informer new file mode 100644 index 0000000000000000000000000000000000000000..5f8979c78343509535036076e6ae191b70aad987 --- /dev/null +++ b/chatbot/training/nvidia-bootc/duplicated/common/usr/libexec/upgrade-informer @@ -0,0 +1,37 @@ +#!/bin/bash + +# Run the command and capture its output +output=$(bootc upgrade --check | sed -e 1q) +message_file="/etc/motd.d/upgrade-message" +bootc_auth="/etc/ostree/auth.json" + +if [[ $output == Update\ available* ]]; then + if [[ ! -f $message_file ]]; then + echo "New version was found" + bootc_image=$(awk '{print $4}' <<< "$output") + # If auth file exists we should use it + auth_params="" + if [[ -f $bootc_auth ]]; then + auth_params="--authfile $bootc_auth" + fi + + # Get image version + # shellcheck disable=SC2086 + image_version_id=$(skopeo inspect --format json $auth_params "$bootc_image" | jq -r '.Labels | .["image_version_id"] // empty') + + # If upgrade available, write the output to the file + cat > $message_file << EOF + +** Attention! ** +** A new $image_version_id version is available ** +** In order to apply it run: bootc upgrade --apply +** Please note that the system will reboot after the upgrade ** + +EOF + fi +else + echo "No upgrade was found" + rm $message_file 2> /dev/null +fi + +echo "Finished running upgrade informer" diff --git a/chatbot/training/nvidia-bootc/duplicated/ilab-wrapper/ilab b/chatbot/training/nvidia-bootc/duplicated/ilab-wrapper/ilab new file mode 100644 index 0000000000000000000000000000000000000000..ae0526b86bb118ffce580e939151f0a334e17bdc --- /dev/null +++ b/chatbot/training/nvidia-bootc/duplicated/ilab-wrapper/ilab @@ -0,0 +1,101 @@ +#!/bin/bash + +echo-err() { echo "$@" >&2; } + +check_insights() { + if [[ -f /etc/insights-client/machine-id ]]; then + return + fi + if [[ -f /etc/ilab/insights-opt-out ]]; then + return + fi + local ID + eval "$(grep ^ID= /etc/os-release)" + if [[ "$ID" != "rhel" ]]; then + return + fi + cat << EOF +This host is not connected to Red Hat Insights. + +To connect this host to Red Hat Insights run the following command: +sudo rhc connect --organization --activation-key + +To generate an Activation Key: +https://console.redhat.com/insights/connector/activation-keys (this page will also display your Organization ID). + +For more information on Red Hat Insights, please visit: +https://docs.redhat.com/en/documentation/subscription_central/1-latest/html/getting_started_with_activation_keys_on_the_hybrid_cloud_console/assembly-creating-managing-activation-keys +EOF + exit 1 +} + +check_insights + +# Template values replaced by container build +CONTAINER_DEVICE="__REPLACE_CONTAINER_DEVICE__" +IMAGE_NAME="__REPLACE_IMAGE_NAME__" + +ENTRYPOINT="ilab" +PARAMS=("$@") + +if [[ -n "$ILAB_HOME" ]]; then + HOME="$ILAB_HOME" +fi + +for dir in "$HOME/.cache" "$HOME/.config" "$HOME/.local"; do + mkdir -p "$dir" +done + +if [[ "$1" = "shell" ]]; then + ENTRYPOINT=bash + PARAMS=() +fi + +# If you need to mount additional volumes into the container, you can specify them +# using the ILAB_ADDITIONAL_MOUNTS environment variable. +# +# Example ILAB_ADDITIONAL_MOUNTS usage: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path /host/path2:/container/path2" +# +# If your path contains spaces, you can use quotes: +# +# ILAB_ADDITIONAL_MOUNTS="/host/path:/container/path '/host/path with spaces':/container/path" +ADDITIONAL_MOUNTS=() +if [ -n "${ILAB_ADDITIONAL_MOUNTS}" ]; then + # (eval is used here to allow the user to specify mounts that might have spaces in them) + eval "ADDITIONAL_MOUNTS=(${ILAB_ADDITIONAL_MOUNTS})" +fi +ADDITIONAL_MOUNT_OPTIONS=() +for PODMAN_MOUNT in "${ADDITIONAL_MOUNTS[@]}"; do + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$PODMAN_MOUNT") +done + +# Add pull-secret to additional mounts +# In case of normal user, /run/user is used (XDG_RUNTIME_DIR), if root, it will be /run/containers +for authfile in \ + "${XDG_RUNTIME_DIR}/containers/auth.json" \ + /run/user/${UID}/containers/auth.json \ + /run/containers/${UID}/auth.json +do + if [[ -f "$authfile" ]]; then + ADDITIONAL_MOUNT_OPTIONS+=("-v" "$authfile:/run/containers/0/auth.json") + break + fi +done + +PODMAN_COMMAND=("podman" "run" "--rm" "-it" + "--device" "${CONTAINER_DEVICE}" + "--security-opt" "label=disable" "--net" "host" + "--shm-size" "10G" + "--pids-limit" "-1" + "-v" "$HOME:$HOME" + "${ADDITIONAL_MOUNT_OPTIONS[@]}" + "--env" "VLLM_LOGGING_LEVEL" + "--env" "HOME" + "--env" "NCCL_DEBUG" + "--entrypoint" "$ENTRYPOINT" + "--env" "HF_TOKEN" + "${IMAGE_NAME}") + +exec "${PODMAN_COMMAND[@]}" "${PARAMS[@]}" diff --git a/chatbot/training/nvidia-bootc/nvidia-toolkit-firstboot.service b/chatbot/training/nvidia-bootc/nvidia-toolkit-firstboot.service new file mode 100644 index 0000000000000000000000000000000000000000..82a36e01e557f30c5ba1ae730e89669f047e2433 --- /dev/null +++ b/chatbot/training/nvidia-bootc/nvidia-toolkit-firstboot.service @@ -0,0 +1,13 @@ +[Unit] +# For more information see https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/cdi-support.html +# It looks like the podman/CDI integration wants a pre-generated list of hardware +Description=Generate /etc/cdi/nvidia.yaml + +[Service] +Type=oneshot +ExecStart=nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml +RemainAfterExit=yes + +[Install] +# TODO: Ensure we have a target that is like "container setup" +WantedBy=multi-user.target diff --git a/chatbot/training/nvidia-bootc/x509-configuration.ini b/chatbot/training/nvidia-bootc/x509-configuration.ini new file mode 100644 index 0000000000000000000000000000000000000000..60fee4139b5a7396be69a69f5778b6927cbcdcb3 --- /dev/null +++ b/chatbot/training/nvidia-bootc/x509-configuration.ini @@ -0,0 +1,15 @@ +[ req ] +default_bits = 4096 +distinguished_name = req_distinguished_name +prompt = no +string_mask = utf8only +x509_extensions = myexts +[ req_distinguished_name ] +O = Project Magma +CN = Project Magma +emailAddress = magma@acme.com +[ myexts ] +basicConstraints=critical,CA:FALSE +keyUsage=digitalSignature +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid diff --git a/chatbot/training/tests/ansible.cfg b/chatbot/training/tests/ansible.cfg new file mode 100644 index 0000000000000000000000000000000000000000..99fb560c0f0f873cf53844ae9d16a055e34d8271 --- /dev/null +++ b/chatbot/training/tests/ansible.cfg @@ -0,0 +1,4 @@ +[ssh_connection] +ssh_common_args = -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ; +[defaults] +host_key_checking = False \ No newline at end of file diff --git a/chatbot/training/tests/e2e-tests/playbook.yml b/chatbot/training/tests/e2e-tests/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..45209deecbeba0cfadfee26f39d4b6f65227d2ee --- /dev/null +++ b/chatbot/training/tests/e2e-tests/playbook.yml @@ -0,0 +1,56 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: root + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 15 + timeout: 180 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: ilab init + shell: ilab init + environment: + HF_TOKEN: "{{ HF_TOKEN }}" + + - name: ilab pull the models for debug currently + shell: ilab download --repository mistralai/Mixtral-8x7B-Instruct-v0.1 + environment: + HF_TOKEN: "{{ HF_TOKEN }}" + + - name: ilab pull the models for debug currently + shell: ilab download --repository ibm/granite-7b-base + environment: + HF_TOKEN: "{{ HF_TOKEN }}" + + - name: Get test script + ansible.builtin.get_url: + url: https://raw.githubusercontent.com/instructlab/instructlab/main/scripts/basic-workflow-tests.sh + dest: /tmp/basic-workflow-tests.sh + mode: 755 + environment: + HF_TOKEN: "{{ HF_TOKEN }}" + + # Allow for debugging with tmate + # - name: Wait for 15 minutes + # pause: + # minutes: 15 + + - name: Run tests + ansible.builtin.shell: /tmp/basic-workflow-tests.sh + register: out + + - name: Test Results - stdout + debug: + msg: "{{out.stdout_lines}}" + + - name: Test Results - stderr + debug: + msg: "{{out.stderr_lines}}" diff --git a/chatbot/training/tests/provision/playbook.yml b/chatbot/training/tests/provision/playbook.yml new file mode 100644 index 0000000000000000000000000000000000000000..fc67d788e1897f69e986e7932549760910b8f9d4 --- /dev/null +++ b/chatbot/training/tests/provision/playbook.yml @@ -0,0 +1,117 @@ +--- +- name: Test Environment Provisioning + hosts: test_environments + remote_user: ec2-user + become: true + gather_facts: false + + tasks: + + - name: Wait until the instance is ready + ansible.builtin.wait_for_connection: + delay: 15 + timeout: 180 + + - name: Gather facts for first time + ansible.builtin.setup: + + - name: Required packages + ansible.builtin.dnf: + name: + - https://s3.eu-west-2.amazonaws.com/amazon-ssm-eu-west-2/latest/linux_amd64/amazon-ssm-agent.rpm + - podman + state: present + disable_gpg_check: true + + - name: Derived Image Containerfile + ansible.builtin.template: + src: ./templates/Containerfile.j2 + dest: /tmp/Containerfile + + - name: Login to default registry + containers.podman.podman_login: + username: "{{ registry_user }}" + password: "{{ registry_password }}" + registry: quay.io + authfile: /etc/containers/auth.json + + - name: Pull the Parent Image + async: 1000 + poll: 0 + register: pull_result + ansible.builtin.shell: | + podman pull "quay.io/ai-lab/{{ image_name }}:latest" \ + --authfile=/etc/containers/auth.json \ + --arch amd64 + + # --retry=3 \ + # --retry-delay=15 \ + + - name: Check on Pulling the parent image + async_status: + jid: "{{ pull_result.ansible_job_id }}" + register: job_result + until: job_result.finished + retries: 100 + delay: 10 + + - name: Build the Bootc Image + async: 1000 + poll: 0 + register: build_result + ansible.builtin.shell: | + podman build . \ + -f /tmp/Containerfile \ + -t quay.io/ai-lab/derived_image:latest \ + --build-arg "sshpubkey={{ ssh_public_key }}" \ + --authfile=/etc/containers/auth.json \ + --pull=never > /tmp/build.log 2>&1 + + # --retry=5 \ + # --retry-delay=15 + + - name: Check on Build Bootc Image + async_status: + jid: "{{ build_result.ansible_job_id }}" + register: job_result + until: job_result.finished + retries: 100 + delay: 10 + + - name: Install the Bootc Image + async: 1000 + poll: 0 + register: install_result + ansible.builtin.shell: | + podman run \ + --authfile=/etc/containers/auth.json \ + --privileged \ + --pid=host \ + --pull=never \ + --rm \ + --security-opt label=type:unconfined_t \ + -v /:/target \ + -v /var/lib/containers:/var/lib/containers quay.io/ai-lab/derived_image:latest \ + bootc install to-existing-root --karg=console=ttyS0,115200n8 --karg=systemd.journald.forward_to_console=1 + + # --retry=5 \ + # --retry-delay=15 \ + + - name: Check on Install Bootc Image + async_status: + jid: "{{ install_result.ansible_job_id }}" + register: job_result + until: job_result.finished + retries: 100 + delay: 10 + + - name: Remove the host from the known_host file + ansible.builtin.known_hosts: + name: "{{ inventory_hostname }}" + state: absent + delegate_to: localhost + + - name: Reboot + ansible.builtin.shell: systemctl reboot + ignore_errors: true + ignore_unreachable: true diff --git a/chatbot/training/tests/provision/requirements.yml b/chatbot/training/tests/provision/requirements.yml new file mode 100644 index 0000000000000000000000000000000000000000..da8ae831f20bf9fe08f229378610bad9112bdc76 --- /dev/null +++ b/chatbot/training/tests/provision/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: containers.podman + version: 1.13.0 diff --git a/chatbot/training/tests/provision/templates/Containerfile.j2 b/chatbot/training/tests/provision/templates/Containerfile.j2 new file mode 100644 index 0000000000000000000000000000000000000000..0df4aa1069c74561e6cb96067448cbe159492b95 --- /dev/null +++ b/chatbot/training/tests/provision/templates/Containerfile.j2 @@ -0,0 +1,13 @@ +FROM quay.io/ai-lab/{{ image_name }}:latest +ARG sshpubkey + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +RUN set -eu && mkdir /usr/etc-system && \ + echo 'AuthorizedKeysFile /usr/etc-system/%u.keys' > /etc/ssh/sshd_config.d/30-auth-system.conf && \ + echo $sshpubkey > /usr/etc-system/root.keys && \ + chmod 0600 /usr/etc-system/root.keys + +RUN dnf install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm diff --git a/chatbot/training/vllm/Containerfile b/chatbot/training/vllm/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..78a1bfda311f3e6f9dbdb3dca8d0b390129e9e23 --- /dev/null +++ b/chatbot/training/vllm/Containerfile @@ -0,0 +1,9 @@ +FROM quay.io/wxpe/tgis-vllm:release.4e3ff78 + +ARG VENDOR='' +LABEL vendor=${VENDOR} +LABEL org.opencontainers.image.vendor=${VENDOR} + +USER root +RUN ln -s /usr/lib64/libcuda.so.1 /usr/lib64/libcuda.so +COPY mixtral.jinja . diff --git a/chatbot/training/vllm/Makefile b/chatbot/training/vllm/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4bfd104c5c6fcd24c66d7396fc4338659a22298a --- /dev/null +++ b/chatbot/training/vllm/Makefile @@ -0,0 +1,19 @@ +CONTAINER_TOOL ?= podman +SOURCE_DATE_EPOCH ?= $(shell git log -1 --pretty=%ct) +BUILD_ARG_FILE ?= + +default: image + +.PHONY: image +image: + @mkdir -p ../build + rm -rf ../build/vllm + "${CONTAINER_TOOL}" build \ + $(ARCH:%=--platform linux/%) \ + $(BUILD_ARG_FILE:%=--build-arg-file=%) \ + $(SOURCE_DATE_EPOCH:%=--timestamp=%) \ + $(VENDOR:%=--build-arg VENDOR=%) \ + --file Containerfile \ + --squash-all \ + --tag oci:../build/vllm \ + ${CONTAINER_TOOL_EXTRA_ARGS} . diff --git a/chatbot/training/vllm/mixtral.jinja b/chatbot/training/vllm/mixtral.jinja new file mode 100644 index 0000000000000000000000000000000000000000..65209bcf56b2a3dbc17a09bcc917acbd41e5e6b7 --- /dev/null +++ b/chatbot/training/vllm/mixtral.jinja @@ -0,0 +1,12 @@ +{% set bos_token = "" %} + +{% set eos_token = "" %} + +{{ bos_token }} +{% for message in messages %} +{% if message['role'] == 'user' %} +{{ '[INST] ' + message['content'] + ' [/INST]' }} +{% elif message['role'] == 'assistant' %} +{{ message['content'] + eos_token}} +{% endif %} +{% endfor %} diff --git a/chatbot/vector_dbs/README.md b/chatbot/vector_dbs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..674a7af0d8e59eb5910c3036490232f8bd4db1bb --- /dev/null +++ b/chatbot/vector_dbs/README.md @@ -0,0 +1,10 @@ +# Directory to store vector_dbs files +This directory has make files and container files for open source vector databases. The built container images are used by recipes like `rag` to provide required database functions. + +## Chroma +[Chroma](https://www.trychroma.com/) is an AI-native open-source embedding database. +Chroma makes it easy to build LLM apps by making knowledge, facts, and skills +pluggable for LLMs. + +## Milvus +[Milvus](https://milvus.io/) is an open-source vector database built to power embedding similarity search and AI applications. It is highly scalable and offers many production ready features for search. diff --git a/chatbot/vector_dbs/chromadb/Containerfile b/chatbot/vector_dbs/chromadb/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..7ad507c589e9c6fcaab8cfc8d3025198d3e6c30d --- /dev/null +++ b/chatbot/vector_dbs/chromadb/Containerfile @@ -0,0 +1,3 @@ +# DO NOT UPVERSION UNTIL APP HAS BEEN UPDATED AS WELL +# AS IT IS THE LAST VERSION THAT WORKS WITH THE RAG RECIPE +FROM docker.io/chromadb/chroma:0.5.16 diff --git a/chatbot/vector_dbs/chromadb/Makefile b/chatbot/vector_dbs/chromadb/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..409edf1e26cb699ae24e29d5b83572f1ef13523b --- /dev/null +++ b/chatbot/vector_dbs/chromadb/Makefile @@ -0,0 +1,7 @@ +CONTAINER_TOOL ?= podman +APP ?= chromadb +APPIMAGE ?= quay.io/ai-lab/${APP}:latest + +.PHONY: build +build: + "${CONTAINER_TOOL}" build -f Containerfile -t ${APPIMAGE} . diff --git a/chatbot/vector_dbs/milvus/Containerfile b/chatbot/vector_dbs/milvus/Containerfile new file mode 100644 index 0000000000000000000000000000000000000000..779a32bcdaa18f33a70d3215b7964d08d9dd87e2 --- /dev/null +++ b/chatbot/vector_dbs/milvus/Containerfile @@ -0,0 +1,2 @@ +FROM docker.io/milvusdb/milvus:master-20240426-bed6363f +ADD embedEtcd.yaml /milvus/configs/embedEtcd.yaml diff --git a/chatbot/vector_dbs/milvus/Makefile b/chatbot/vector_dbs/milvus/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..a004c08d70f1c6e289726ef62001eb2cd56c4be7 --- /dev/null +++ b/chatbot/vector_dbs/milvus/Makefile @@ -0,0 +1,56 @@ +CONTAINER_TOOL ?= podman +REGISTRY ?= quay.io +REGISTRY_ORG ?= ai-lab +COMPONENT = vector_dbs + +IMAGE ?= $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/milvus:latest + +ARCH ?= $(shell uname -m) +PLATFORM ?= linux/$(ARCH) + +gRCP_PORT := 19530 +REST_PORT := 9091 +CLIENT_PORT := 2379 + +LIB_MILVUS_DIR_MOUNTPATH := $(shell pwd)/volumes/milvus + +.PHONY: build +build: + "${CONTAINER_TOOL}" build --platform $(PLATFORM) -f Containerfile -t ${IMAGE} . + +.PHONY: run +run: + podman run -it \ + --name milvus-standalone \ + --security-opt seccomp:unconfined \ + -e ETCD_USE_EMBED=true \ + -e ETCD_CONFIG_PATH=/milvus/configs/embedEtcd.yaml \ + -e COMMON_STORAGETYPE=local \ + -v $(LIB_MILVUS_DIR_MOUNTPATH):/var/lib/milvus \ + -p $(gRCP_PORT):$(gRCP_PORT) \ + -p $(REST_PORT):$(REST_PORT) \ + -p $(CLIENT_PORT):$(CLIENT_PORT) \ + --health-cmd="curl -f http://localhost:$(REST_PORT)/healthz" \ + --health-interval=30s \ + --health-start-period=90s \ + --health-timeout=20s \ + --health-retries=3 \ + $(IMAGE) \ + milvus run standalone 1> /dev/null + +.PHONY: stop +stop: + -podman stop milvus-standalone + +.PHONY: delete +delete: + -podman rm milvus-standalone -f + +.PHONY: podman-clean +podman-clean: + @container_ids=$$(podman ps --format "{{.ID}} {{.Image}}" | awk '$$2 == "$(IMAGE)" {print $$1}'); \ + echo "removing all containers with IMAGE=$(IMAGE)"; \ + for id in $$container_ids; do \ + echo "Removing container: $$id,"; \ + podman rm -f $$id; \ + done diff --git a/chatbot/vector_dbs/milvus/embedEtcd.yaml b/chatbot/vector_dbs/milvus/embedEtcd.yaml new file mode 100644 index 0000000000000000000000000000000000000000..32954faa8b1fc8c62357082670736f3e6ac41587 --- /dev/null +++ b/chatbot/vector_dbs/milvus/embedEtcd.yaml @@ -0,0 +1,5 @@ +listen-client-urls: http://0.0.0.0:2379 +advertise-client-urls: http://0.0.0.0:2379 +quota-backend-bytes: 4294967296 +auto-compaction-mode: revision +auto-compaction-retention: '1000' diff --git a/chatbot/vector_dbs/milvus/volumes/milvus/.gitkeep b/chatbot/vector_dbs/milvus/volumes/milvus/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391