diff --git a/.github/.release-please-manifest.json b/.github/.release-please-manifest.json index 85d60da21..d3fc4b83e 100644 --- a/.github/.release-please-manifest.json +++ b/.github/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.9.2" + ".": "0.10.0" } diff --git a/.github/actions/lfai-core/action.yaml b/.github/actions/lfai-core/action.yaml new file mode 100644 index 000000000..40807f8c3 --- /dev/null +++ b/.github/actions/lfai-core/action.yaml @@ -0,0 +1,28 @@ +name: setup-lfai-core +description: "Setup Supabase and LFAI-API" + +runs: + using: composite + steps: + - name: Deploy Supabase + shell: bash + run: | + make build-supabase LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf tools kubectl create namespace leapfrogai + uds zarf package deploy packages/supabase/zarf-package-supabase-amd64-e2e-test.tar.zst --confirm + rm packages/supabase/zarf-package-supabase-amd64-e2e-test.tar.zst + + - name: Set environment variable + shell: bash + id: set-env-var + run: | + echo "ANON_KEY=$(uds zarf tools kubectl get secret supabase-bootstrap-jwt -n leapfrogai -o jsonpath='{.data.anon-key}' | base64 -d)" >> "$GITHUB_ENV" + + - name: Deploy LFAI-API + shell: bash + run: | + make build-api LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/api/zarf-package-leapfrogai-api-amd64-e2e-test.tar.zst --confirm + rm packages/api/zarf-package-leapfrogai-api-amd64-e2e-test.tar.zst diff --git a/.github/actions/python/action.yaml b/.github/actions/python/action.yaml new file mode 100644 index 000000000..12385af26 --- /dev/null +++ b/.github/actions/python/action.yaml @@ -0,0 +1,23 @@ +name: setup-python +description: "Setup Python and library dependencies" + +inputs: + additionalOptionalDep: + description: "Additional optional dependencies to install" + +runs: + using: composite + steps: + - name: Setup Python + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 + with: + python-version-file: 'pyproject.toml' + + - name: Install Python Deps + shell: bash + run: python -m pip install ".[dev]" + + - name: Install additionalDeps + if: ${{ inputs.additionalOptionalDep != '' }} + shell: bash + run: python -m pip install ".[${{ inputs.additionalOptionalDep }}]" diff --git a/.github/actions/uds-cluster/action.yaml b/.github/actions/uds-cluster/action.yaml new file mode 100644 index 000000000..19b23c61e --- /dev/null +++ b/.github/actions/uds-cluster/action.yaml @@ -0,0 +1,22 @@ +name: setup-uds +description: "Setup UDS Cluster" + +inputs: + registry1Username: + description: Registry1 Username + registry1Password: + description: Registry1 Password + +runs: + using: composite + steps: + - name: Setup UDS Environment + uses: defenseunicorns/uds-common/.github/actions/setup@05f42bb3117b66ebef8c72ae050b34bce19385f5 + with: + username: ${{ inputs.registry1Username }} + password: ${{ inputs.registry1Password }} + + - name: Create UDS Cluster + shell: bash + run: | + make create-uds-cpu-cluster \ No newline at end of file diff --git a/.github/workflows/e2e-llama-cpp-python.yaml b/.github/workflows/e2e-llama-cpp-python.yaml new file mode 100644 index 000000000..66cd16bfb --- /dev/null +++ b/.github/workflows/e2e-llama-cpp-python.yaml @@ -0,0 +1,87 @@ +# End-to-end testing that deploys Supabase and the API, and deploy/tests llama-cpp-python, text-embeddings, and whisper + +name: e2e-llama-cpp-python +on: + pull_request: + types: + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs + paths: + # Catch-all + - "**" + + # Ignore updates to the .github directory, unless it's this current file + - "!.github/**" + - ".github/workflows/e2e-llama-cpp-python.yaml" + - ".github/actions/uds-cluster/action.yaml" + + # Ignore docs and website things + - "!**.md" + - "!docs/**" + - "!adr/**" + - "!website/**" + - "!netlify.toml" + + # Ignore updates to generic github metadata files + - "!CODEOWNERS" + - "!.gitignore" + - "!LICENSE" + + # Ignore local development files + - "!.pre-commit-config.yaml" + + # Ignore non e2e tests changes + - "!tests/pytest/**" + + # Ignore LFAI-UI source code changes + - "!src/leapfrogai_ui/**" + + # Ignore changes to unrelated packages + - "!packages/k3d-gpu/**" + - "!packages/repeater/**" + - "!packages/text-embeddings/**" + - "!packages/ui/**" + - "!packages/vllm/**" + - "!packages/whisper/**" + +concurrency: + group: e2e-llama-cpp-python-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e_llama: + runs-on: ai-ubuntu-big-boy-8-core + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Python + uses: ./.github/actions/python + + - name: Setup UDS Cluster + uses: ./.github/actions/uds-cluster + with: + registry1Username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + registry1Password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + + - name: Setup LFAI-API and Supabase + uses: ./.github/actions/lfai-core + + ########## + # llama + ########## + - name: Deploy llama-cpp-python + run: | + make build-llama-cpp-python LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/llama-cpp-python/zarf-package-llama-cpp-python-amd64-e2e-test.tar.zst -l=trace --confirm + rm packages/llama-cpp-python/zarf-package-llama-cpp-python-amd64-e2e-test.tar.zst + + - name: Test llama-cpp-python + run: | + python -m pytest ./tests/e2e/test_llama.py -v diff --git a/.github/workflows/e2e-playwright.yaml b/.github/workflows/e2e-playwright.yaml new file mode 100644 index 000000000..671e4d910 --- /dev/null +++ b/.github/workflows/e2e-playwright.yaml @@ -0,0 +1,122 @@ +# End-to-end testing that deploys and tests Supabase, API, UI, and VLLM + +name: e2e-playwright +on: + pull_request: + types: + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs + paths: + # Catch-all + - "**" + + # Ignore updates to the .github directory, unless it's this current file + - "!.github/**" + - ".github/workflows/e2e-playwright.yaml" + - ".github/actions/uds-cluster/action.yaml" + + # Ignore docs and website things + - "!**.md" + - "!docs/**" + - "!adr/**" + - "!website/**" + - "!netlify.toml" + + # Ignore updates to generic github metadata files + - "!CODEOWNERS" + - "!.gitignore" + - "!LICENSE" + + # Ignore local development files + - "!.pre-commit-config.yaml" + + # Ignore non e2e tests changes + - "!tests/pytest/**" + + # Ignore changes to unrelated packages + - "!packages/k3d-gpu/**" + - "!packages/llama-cpp-python/**" + - "!packages/repeater/**" + - "!packages/text-embeddings/**" + - "!packages/vllm/**" + - "!packages/whisper/**" + + + +concurrency: + group: e2e-playwright-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e_playwright: + runs-on: ai-ubuntu-big-boy-8-core + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version-file: 'src/leapfrogai_ui/package.json' + + - name: Install UI/Playwright Dependencies + run: | + npm --prefix src/leapfrogai_ui ci + npx --prefix src/leapfrogai_ui playwright install + + - name: Setup Python + uses: ./.github/actions/python + + - name: Setup UDS Cluster + uses: ./.github/actions/uds-cluster + with: + registry1Username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + registry1Password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + + - name: Setup LFAI-API and Supabase + uses: ./.github/actions/lfai-core + + - name: Test Supabase + run: | + python -m pip install requests + python -m pytest ./tests/e2e/test_supabase.py -v + + - name: Test API + run: | + python -m pip install requests + python -m pytest ./tests/e2e/test_api.py -v + + ########## + # UI + ########## + - name: Deploy LFAI-UI + run: | + make build-ui LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/ui/zarf-package-leapfrogai-ui-amd64-e2e-test.tar.zst --confirm + rm packages/ui/zarf-package-leapfrogai-ui-amd64-e2e-test.tar.zst + + # Run the playwright UI tests using the deployed Supabase endpoint and upload report as an artifact + - name: UI/API/Supabase E2E Playwright Tests + run: | + cp src/leapfrogai_ui/.env.example src/leapfrogai_ui/.env + TEST_ENV=CI PUBLIC_DISABLE_KEYCLOAK=true PUBLIC_SUPABASE_ANON_KEY=$ANON_KEY npm --prefix src/leapfrogai_ui run test:integration:ci + + # Upload the Playwright report as an artifact + - name: Archive Playwright Report + uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: src/leapfrogai_ui/e2e-report/ + retention-days: 30 + + # The UI can be removed after the Playwright tests are finished + - name: Cleanup UI + run: | + uds zarf package remove leapfrogai-ui --confirm diff --git a/.github/workflows/e2e-shim.yaml b/.github/workflows/e2e-shim.yaml index 83899dcce..c81f42ea1 100644 --- a/.github/workflows/e2e-shim.yaml +++ b/.github/workflows/e2e-shim.yaml @@ -1,34 +1,7 @@ # Catch all the things we ignore in the e2e workflow name: e2e Skip Shim on: - pull_request: - paths: - # Catch updates to the .github directory, unless it is the e2e.yaml files - - ".github/**" - - "!.github/workflows/e2e.yaml" - - # Catch docs and website things - - "**.md" - - "adr/**" - - "docs/**" - - "website/**" - - "netlify.toml" - - # Catch generic github metadata files - - "CODEOWNERS" - - ".gitignore" - - "LICENSE" - - ".pre-commit-config.yaml" - - # Catch pytests - - "tests/pytest/**" - - # Catch LFAI-UI things - - "src/leapfrogai_ui/**" - - "packages/ui/**" - - # Catch changes to the repeater model - - "packages/repeater/**" + pull_request permissions: @@ -51,7 +24,31 @@ concurrency: jobs: - e2e: + e2e_llama: + runs-on: ubuntu-latest + steps: + - name: Skipped + run: | + echo skipped + e2e_playwright: + runs-on: ubuntu-latest + steps: + - name: Skipped + run: | + echo skipped + e2e_text_embeddings: + runs-on: ubuntu-latest + steps: + - name: Skipped + run: | + echo skipped + e2e_vllm: + runs-on: ubuntu-latest + steps: + - name: Skipped + run: | + echo skipped + e2e_whisper: runs-on: ubuntu-latest steps: - name: Skipped diff --git a/.github/workflows/e2e-text-embeddings.yaml b/.github/workflows/e2e-text-embeddings.yaml new file mode 100644 index 000000000..eba01378d --- /dev/null +++ b/.github/workflows/e2e-text-embeddings.yaml @@ -0,0 +1,89 @@ +# End-to-end testing that deploys Supabase and the API, and deploy/tests llama-cpp-python, text-embeddings, and whisper + +name: e2e-text-embeddings +on: + pull_request: + types: + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs + paths: + # Catch-all + - "**" + + # Ignore updates to the .github directory, unless it's this current file + - "!.github/**" + - ".github/workflows/e2e-text-embeddings.yaml" + - ".github/actions/uds-cluster/action.yaml" + + # Ignore docs and website things + - "!**.md" + - "!docs/**" + - "!adr/**" + - "!website/**" + - "!netlify.toml" + + # Ignore updates to generic github metadata files + - "!CODEOWNERS" + - "!.gitignore" + - "!LICENSE" + + # Ignore local development files + - "!.pre-commit-config.yaml" + + # Ignore non e2e tests changes + - "!tests/pytest/**" + + # Ignore LFAI-UI source code changes + - "!src/leapfrogai_ui/**" + + # Ignore changes to unrelated packages + - "!packages/k3d-gpu/**" + - "!packages/llama-cpp-python/**" + - "!packages/repeater/**" + - "!packages/ui/**" + - "!packages/vllm/**" + - "!packages/whisper/**" + + + +concurrency: + group: e2e-text-embeddings-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e_text_embeddings: + runs-on: ai-ubuntu-big-boy-8-core + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Python + uses: ./.github/actions/python + + - name: Setup UDS Cluster + uses: ./.github/actions/uds-cluster + with: + registry1Username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + registry1Password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + + - name: Setup LFAI-API and Supabase + uses: ./.github/actions/lfai-core + + ########## + # text-embeddings + ########## + - name: Deploy text-embeddings + run: | + make build-text-embeddings LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm + rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst + + - name: Test text-embeddings + run: | + python -m pytest ./tests/e2e/test_text_embeddings.py -v diff --git a/.github/workflows/e2e-vllm.yaml b/.github/workflows/e2e-vllm.yaml new file mode 100644 index 000000000..cedfab53e --- /dev/null +++ b/.github/workflows/e2e-vllm.yaml @@ -0,0 +1,83 @@ +# End-to-end testing that deploys and tests Supabase, API, UI, and VLLM + +name: e2e-vllm +on: + pull_request: + types: + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs + paths: + # Catch-all + - "**" + + # Ignore updates to the .github directory, unless it's this current file + - "!.github/**" + - ".github/workflows/e2e-vllm.yaml" + - ".github/actions/uds-cluster/action.yaml" + + # Ignore docs and website things + - "!**.md" + - "!docs/**" + - "!adr/**" + - "!website/**" + - "!netlify.toml" + + # Ignore updates to generic github metadata files + - "!CODEOWNERS" + - "!.gitignore" + - "!LICENSE" + + # Ignore local development files + - "!.pre-commit-config.yaml" + + # Ignore non e2e tests changes + - "!tests/pytest/**" + + # Ignore LFAI-UI source code changes + - "!src/leapfrogai_ui/**" + + # Ignore changes to unrelated packages + - "!packages/k3d-gpu/**" + - "!packages/llama-cpp-python/**" + - "!packages/repeater/**" + - "!packages/text-embeddings/**" + - "!packages/ui/**" + - "!packages/whisper/**" + + + +concurrency: + group: e2e-vllm-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e_vllm: + runs-on: ai-ubuntu-big-boy-8-core + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Python + uses: ./.github/actions/python + with: + additionalOptionalDep: dev-vllm + + - name: Setup UDS Environment + uses: defenseunicorns/uds-common/.github/actions/setup@05f42bb3117b66ebef8c72ae050b34bce19385f5 + with: + username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + + ########## c + # vLLM + # NOTE: We are not deploying and testing vLLM in this workflow because it requires a GPU + # : This workflow simply verifies that the vLLM package can be built + ########## + - name: Build vLLM + run: | + make build-vllm LOCAL_VERSION=e2e-test diff --git a/.github/workflows/e2e-whisper.yaml b/.github/workflows/e2e-whisper.yaml new file mode 100644 index 000000000..7c3908aa5 --- /dev/null +++ b/.github/workflows/e2e-whisper.yaml @@ -0,0 +1,89 @@ +# End-to-end testing that deploys Supabase and the API, and deploy/tests llama-cpp-python, text-embeddings, and whisper + +name: e2e-whisper +on: + pull_request: + types: + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs + paths: + # Catch-all + - "**" + + # Ignore updates to the .github directory, unless it's this current file + - "!.github/**" + - ".github/workflows/e2e-whisper.yaml" + - ".github/actions/uds-cluster/action.yaml" + + # Ignore docs and website things + - "!**.md" + - "!docs/**" + - "!adr/**" + - "!website/**" + - "!netlify.toml" + + # Ignore updates to generic github metadata files + - "!CODEOWNERS" + - "!.gitignore" + - "!LICENSE" + + # Ignore local development files + - "!.pre-commit-config.yaml" + + # Ignore non e2e tests changes + - "!tests/pytest/**" + + # Ignore LFAI-UI source code changes + - "!src/leapfrogai_ui/**" + + # Ignore changes to unrelated packages + - "!packages/k3d-gpu/**" + - "!packages/llama-cpp-python/**" + - "!packages/repeater/**" + - "!packages/text-embeddings/**" + - "!packages/ui/**" + - "!packages/vllm/**" + +concurrency: + group: e2e-whisper-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e_whisper: + runs-on: ai-ubuntu-big-boy-8-core + if: ${{ !github.event.pull_request.draft }} + + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Python + uses: ./.github/actions/python + with: + additionalOptionalDep: dev-whisper + + - name: Setup UDS Cluster + uses: ./.github/actions/uds-cluster + with: + registry1Username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} + registry1Password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} + + - name: Setup LFAI-API and Supabase + uses: ./.github/actions/lfai-core + + ########## + # whisper + ########## + - name: Deploy whisper + run: | + make build-whisper LOCAL_VERSION=e2e-test + docker image prune -af + uds zarf package deploy packages/whisper/zarf-package-whisper-amd64-e2e-test.tar.zst -l=trace --confirm + rm packages/whisper/zarf-package-whisper-amd64-e2e-test.tar.zst + + - name: Test whisper + run: | + python -m pytest ./tests/e2e/test_whisper.py -v diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml deleted file mode 100644 index adae970fe..000000000 --- a/.github/workflows/e2e.yaml +++ /dev/null @@ -1,207 +0,0 @@ -name: e2e -on: - pull_request: - types: - - ready_for_review - - review_requested - - synchronize - - milestoned - paths: - # Catch-all - - "**" - - # Ignore updates to the .github directory, unless it's this current file - - "!.github/**" - - ".github/workflows/e2e.yaml" - - # Ignore docs and website things - - "!**.md" - - "!docs/**" - - "!adr/**" - - "!website/**" - - "!netlify.toml" - - # Ignore updates to generic github metadata files - - "!CODEOWNERS" - - "!.gitignore" - - "!LICENSE" - - # Ignore local development files - - "!.pre-commit-config.yaml" - - # Ignore non e2e tests - - "!tests/pytest/**" - - # Ignore LFAI-UI things (for now?) - - "!src/leapfrogai_ui/**" - - "!packages/ui/**" - - # Ignore changes to the repeater model - - "!packages/repeater/**" - - -concurrency: - group: e2e-${{ github.ref }} - cancel-in-progress: true - -jobs: - e2e: - runs-on: ai-ubuntu-big-boy-8-core - if: ${{ !github.event.pull_request.draft }} - - steps: - - name: Checkout Repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Setup Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c #v5.0.0 - with: - python-version-file: 'pyproject.toml' - - - name: Install Python Deps - run: python -m pip install "." - - - name: Setup Node - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version-file: 'src/leapfrogai_ui/package.json' - - - name: Install UI/Playwright Dependencies - run: | - npm --prefix src/leapfrogai_ui ci - npx --prefix src/leapfrogai_ui playwright install - - - name: Setup UDS Environment - uses: defenseunicorns/uds-common/.github/actions/setup@05f42bb3117b66ebef8c72ae050b34bce19385f5 - with: - username: ${{ secrets.IRON_BANK_ROBOT_USERNAME }} - password: ${{ secrets.IRON_BANK_ROBOT_PASSWORD }} - - - name: Create UDS Cluster - run: | - uds deploy k3d-core-slim-dev:0.23.0 --confirm - - ########## - # Supabase - ########## - - name: Deploy Supabase - run: | - make build-supabase LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf tools kubectl create namespace leapfrogai - uds zarf package deploy packages/supabase/zarf-package-supabase-amd64-e2e-test.tar.zst --confirm - rm packages/supabase/zarf-package-supabase-amd64-e2e-test.tar.zst - - - name: Set environment variable - id: set-env-var - run: | - echo "ANON_KEY=$(uds zarf tools kubectl get secret supabase-bootstrap-jwt -n leapfrogai -o jsonpath='{.data.anon-key}' | base64 -d)" >> "$GITHUB_ENV" - - - name: Test Supabase - run: | - python -m pip install requests - python -m pytest ./tests/e2e/test_supabase.py -v - - ########## - # UI - ########## - - name: Deploy LFAI-UI - run: | - make build-ui LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/ui/zarf-package-leapfrogai-ui-amd64-e2e-test.tar.zst --confirm - rm packages/ui/zarf-package-leapfrogai-ui-amd64-e2e-test.tar.zst - - ########## - # API - ########## - - name: Deploy LFAI-API - run: | - make build-api LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/api/zarf-package-leapfrogai-api-amd64-e2e-test.tar.zst --confirm - rm packages/api/zarf-package-leapfrogai-api-amd64-e2e-test.tar.zst - - - name: Test API - run: | - python -m pip install requests - python -m pytest ./tests/e2e/test_api.py -v - - # Run the playwright UI tests using the deployed Supabase endpoint - - name: UI/API/Supabase E2E Playwright Tests - run: | - cp src/leapfrogai_ui/.env.example src/leapfrogai_ui/.env - TEST_ENV=CI PUBLIC_DISABLE_KEYCLOAK=true PUBLIC_SUPABASE_ANON_KEY=$ANON_KEY npm --prefix src/leapfrogai_ui run test:integration:ci - - # The UI can be removed after the Playwright tests are finished - - name: Cleanup UI - run: | - uds zarf package remove leapfrogai-ui --confirm - - ########## - # llama - ########## - - name: Deploy llama-cpp-python - run: | - make build-llama-cpp-python LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/llama-cpp-python/zarf-package-llama-cpp-python-amd64-e2e-test.tar.zst -l=trace --confirm - rm packages/llama-cpp-python/zarf-package-llama-cpp-python-amd64-e2e-test.tar.zst - - - name: Test llama-cpp-python - run: | - python -m pytest ./tests/e2e/test_llama.py -v - - - name: Cleanup llama-cpp-python - run: | - uds zarf package remove llama-cpp-python -l=trace --confirm - - ########## - # text-embeddings - ########## - - name: Deploy text-embeddings - run: | - make build-text-embeddings LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst -l=trace --confirm - rm packages/text-embeddings/zarf-package-text-embeddings-amd64-e2e-test.tar.zst - - - name: Test text-embeddings - run: | - python -m pytest ./tests/e2e/test_text_embeddings.py -v - - - name: Cleanup text-embeddings - run: | - uds zarf package remove text-embeddings -l=trace --confirm - - ########## - # whisper - ########## - - name: Deploy whisper - run: | - make build-whisper LOCAL_VERSION=e2e-test - docker image prune -af - uds zarf package deploy packages/whisper/zarf-package-whisper-amd64-e2e-test.tar.zst -l=trace --confirm - rm packages/whisper/zarf-package-whisper-amd64-e2e-test.tar.zst - - - name: Test whisper - run: | - python -m pytest ./tests/e2e/test_whisper.py -v - - - name: Cleanup whisper - run: | - uds zarf package remove whisper -l=trace --confirm - - # This cleanup may need to be moved/removed when other packages depend on Supabase - - name: Cleanup Supabase - run: | - uds zarf package remove supabase -l=trace --confirm - - ########## - # vLLM - # NOTE: We are not deploying and testing vLLM in this workflow because it requires a GPU - # : This workflow simply verifies that the vLLM package can be built - ########## - - name: Build vLLM - run: | - make build-vllm LOCAL_VERSION=e2e-test diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 7450aa4ee..e4cca5a4b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -2,10 +2,11 @@ name: lint on: pull_request: types: - - ready_for_review - - review_requested - - synchronize - - milestoned + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs # Declare default permissions as read only. permissions: read-all diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 1dda8ce1a..87cac7d56 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -2,10 +2,11 @@ name: pytest on: pull_request: types: - - ready_for_review - - review_requested - - synchronize - - milestoned + - opened # default trigger + - reopened # default trigger + - synchronize # default trigger + - ready_for_review # don't run on draft PRs + - milestoned # allows us to trigger on bot PRs paths: - "**" - "!.github/**" @@ -56,7 +57,7 @@ jobs: run: docker run -p 50051:50051 -d --name=repeater ghcr.io/defenseunicorns/leapfrogai/repeater:dev - name: Install Python Deps - run: pip install "." "src/leapfrogai_api" "src/leapfrogai_sdk" + run: pip install ".[dev]" "src/leapfrogai_api" "src/leapfrogai_sdk" - name: Run Pytest run: python -m pytest tests/pytest -v diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0f891a599..c03767b30 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,6 +40,10 @@ jobs: with: python-version-file: 'pyproject.toml' + - name: Instal Python Deps + run: | + python -m pip install ".[dev,dev-whisper,dev-vllm]" + - name: Build and Publish k3d-gpu image run: | cd packages/k3d-gpu diff --git a/.gitignore b/.gitignore index e4e9d2d60..bcefc6ea7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # temporary deployment and development artifacts +.logs/ data/* **/*.tar.zst __pycache__ diff --git a/CHANGELOG.md b/CHANGELOG.md index 85aa2eaad..a23950ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Changelog +## [0.10.0](https://github.com/defenseunicorns/leapfrogai/compare/v0.9.2...v0.10.0) (2024-08-02) + + +### ⚠ BREAKING CHANGES + +* **api:** updating api endpoints ([#817](https://github.com/defenseunicorns/leapfrogai/issues/817)) + +### Features + +* **backend:** add k3d gpu image builder ([#797](https://github.com/defenseunicorns/leapfrogai/issues/797)) ([4504085](https://github.com/defenseunicorns/leapfrogai/commit/4504085dc10e9b6cb74115c974ea5e8f8c7fa98c)) +* **backend:** nvidia runtimeclass ([#787](https://github.com/defenseunicorns/leapfrogai/issues/787)) ([106997d](https://github.com/defenseunicorns/leapfrogai/commit/106997d42e750014de94959c939442ac4e23378b)) +* silent parallel make targets for build and deployment ([#824](https://github.com/defenseunicorns/leapfrogai/issues/824)) ([681aafd](https://github.com/defenseunicorns/leapfrogai/commit/681aafdec1b59f00fb8c8deb598a00327ef404b6)) +* **ui:** remove carbon and replace with Flowbite ([#862](https://github.com/defenseunicorns/leapfrogai/issues/862)) ([921a864](https://github.com/defenseunicorns/leapfrogai/commit/921a8642c3e70c4d0dc3d92cd0d7745e45e21524)) + + +### Bug Fixes + +* **api:** fix indexing files with api key auth ([#852](https://github.com/defenseunicorns/leapfrogai/issues/852)) ([c4d9c3f](https://github.com/defenseunicorns/leapfrogai/commit/c4d9c3f289f8708cd08a82d290fe9ab51ba8cc09)) +* helm template evluation for whisper GPU_REQUEST envvar ([#859](https://github.com/defenseunicorns/leapfrogai/issues/859)) ([5320890](https://github.com/defenseunicorns/leapfrogai/commit/532089087fd520121c64d331716945563509a062)) +* make errors when supabase is unavailable ([#814](https://github.com/defenseunicorns/leapfrogai/issues/814)) ([976635c](https://github.com/defenseunicorns/leapfrogai/commit/976635cd5c5d615cc16adcbf6d6622845ab64eca)) +* **ui:** logout ([#849](https://github.com/defenseunicorns/leapfrogai/issues/849)) ([f71af5d](https://github.com/defenseunicorns/leapfrogai/commit/f71af5d6c0d6432644144f0ad090100969c69c04)) +* **ui:** playwright login without keycloak ([#833](https://github.com/defenseunicorns/leapfrogai/issues/833)) ([fd1e3dd](https://github.com/defenseunicorns/leapfrogai/commit/fd1e3dd3148581307d9ccf021c94d4208d839c56)) +* **whisper:** Including missing cuda dependencies required for GPU runtimes ([2aba4af](https://github.com/defenseunicorns/leapfrogai/commit/2aba4af86b37d32fbb4a8cbe73b6151e2e1c3190)) + + +### Miscellaneous + +* **api:** Adds API load testing ([#801](https://github.com/defenseunicorns/leapfrogai/issues/801)) ([67b9755](https://github.com/defenseunicorns/leapfrogai/commit/67b9755f8ff8ed2c1197efe9f22d12ace685ef82)) +* **api:** Indexing performance improvements ([#799](https://github.com/defenseunicorns/leapfrogai/issues/799)) ([e679ad2](https://github.com/defenseunicorns/leapfrogai/commit/e679ad22f52cb411e47fcc01f6e3eb3d7191ffcd)) +* **api:** updating api endpoints ([#817](https://github.com/defenseunicorns/leapfrogai/issues/817)) ([6ff292f](https://github.com/defenseunicorns/leapfrogai/commit/6ff292f0d3462b0a12cb0bd032ed9bdaa4077d69)) +* **deps:** bump torch from 2.1.2 to 2.2.0 in /packages/text-embeddings ([#831](https://github.com/defenseunicorns/leapfrogai/issues/831)) ([22c75cb](https://github.com/defenseunicorns/leapfrogai/commit/22c75cbfb662f8f1f53d91875d9bdf5126c31900)) +* make python test dependencies optional ([#815](https://github.com/defenseunicorns/leapfrogai/issues/815)) ([89ff0a6](https://github.com/defenseunicorns/leapfrogai/commit/89ff0a6a34957bbcd788250c10e28312192f0af1)) +* optimize vLLM Dockerfile to reduce layer sizes ([#805](https://github.com/defenseunicorns/leapfrogai/issues/805)) ([0fec864](https://github.com/defenseunicorns/leapfrogai/commit/0fec864f5f870dee560bd60dac74bca11b219442)) +* release 0.10.0 ([#864](https://github.com/defenseunicorns/leapfrogai/issues/864)) ([7e6f574](https://github.com/defenseunicorns/leapfrogai/commit/7e6f57433c0b2ea49f814906d9625f7419baecf3)) +* Remove model weights from container images ([#786](https://github.com/defenseunicorns/leapfrogai/issues/786)) ([33e4efb](https://github.com/defenseunicorns/leapfrogai/commit/33e4efb3031e7fd95333c4f98d47933a8123cb2d)) +* restrict daemonset to cuda compute, utility ([#836](https://github.com/defenseunicorns/leapfrogai/issues/836)) ([4bf9124](https://github.com/defenseunicorns/leapfrogai/commit/4bf9124f2019cd9c20abfc3db717de5fd5c16d95)) +* set kong service to default to cluster IP ([#857](https://github.com/defenseunicorns/leapfrogai/issues/857)) ([08f1d10](https://github.com/defenseunicorns/leapfrogai/commit/08f1d109f571e6c1649261cc99da115e23675b5d)) +* split e2e tests into multiple workflows ([#808](https://github.com/defenseunicorns/leapfrogai/issues/808)) ([c993ad5](https://github.com/defenseunicorns/leapfrogai/commit/c993ad5526e0bf23148e5f9ceef215abadef30fe)) +* Update defenseunicorns/zarf to zarf-dev/zarf ([#832](https://github.com/defenseunicorns/leapfrogai/issues/832)) ([cc18cea](https://github.com/defenseunicorns/leapfrogai/commit/cc18ceabfedf88bf355ec4eb53b436443b9a1f3c)) +* update release workflow to install necessary python deps ([#867](https://github.com/defenseunicorns/leapfrogai/issues/867)) ([1e667a4](https://github.com/defenseunicorns/leapfrogai/commit/1e667a44504e2441549c60951d8f1a1757c81864)) +* **whisper:** Pass through variables down to whisper ([#840](https://github.com/defenseunicorns/leapfrogai/issues/840)) ([4e8092a](https://github.com/defenseunicorns/leapfrogai/commit/4e8092ac4fe2ba9fb369506773074e929a1a1a25)) + ## [0.9.2](https://github.com/defenseunicorns/leapfrogai/compare/v0.9.1...v0.9.2) (2024-07-19) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15aba8464..b764d1ccd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,8 +26,8 @@ Specifically: 3. :key: Set up your Git config to GPG sign all commits. [Here's some documentation on how to set it up](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). You won't be able to merge your PR if you have any unverified commits. 4. Create a Draft Pull Request as soon as you can, even if it is just 5 minutes after you started working on it. We lean towards working in the open as much as we can. If you're not sure what to put in the PR description, just put a link to the issue you're working on. If you're not sure what to put in the PR title, just put "WIP" (Work In Progress) and we'll help you out with the rest. 5. :key: Automated tests will begin based on the paths you have edited in your Pull Request. - > ⚠️ **NOTE:** _If you are an external third-party contributor, the pipelines won't run until a [CODEOWNER](https://github.com/defenseunicorns/zarf/blob/main/CODEOWNERS) approves the pipeline run._ -6. :key: Be sure to use the [needs-adr,needs-docs,needs-tests](https://github.com/defenseunicorns/zarf/labels?q=needs) labels as appropriate for the PR. Once you have addressed all of the needs, remove the label. + > ⚠️ **NOTE:** _If you are an external third-party contributor, the pipelines won't run until a [CODEOWNER](https://github.com/zarf-dev/zarf/blob/main/CODEOWNERS) approves the pipeline run._ +6. :key: Be sure to use the [needs-adr,needs-docs,needs-tests](https://github.com/zarf-dev/zarf/labels?q=needs) labels as appropriate for the PR. Once you have addressed all of the needs, remove the label. 7. Once the review is complete and approved, a core member of the LeapfrogAI project will merge your PR. If you are an external third-party contributor, two core members of the zarf project will be required to approve the PR. 8. Close the issue if it is fully resolved by your PR. _Hint: You can add "Fixes #XX" to the PR description to automatically close an issue when the PR is merged._ diff --git a/Makefile b/Makefile index bff055321..bf5442755 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,12 @@ ARCH ?= amd64 -KEY ?= "" REG_PORT ?= 5000 REG_NAME ?= registry - -VERSION ?= $(shell git describe --abbrev=0 --tags) LOCAL_VERSION ?= $(shell git rev-parse --short HEAD) +DOCKER_FLAGS := +ZARF_FLAGS := +SILENT_DOCKER_FLAGS := --quiet +SILENT_ZARF_FLAGS := --no-progress -l warn --no-color +MAX_JOBS := 4 ###################################################################################### .PHONY: help @@ -13,8 +15,8 @@ help: ## Display this help information | sort | awk 'BEGIN {FS = ":.*?## "}; \ {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' - clean: ## Clean up all the things (packages, build dirs, compiled .whl files, python eggs) + -rm -rf .logs -rm zarf-package-*.tar.zst -rm packages/**/zarf-package-*.tar.zst -rm -rf build/* @@ -46,113 +48,115 @@ clean-registry: @docker rm ${REG_NAME} sdk-wheel: ## build wheels for the leapfrogai_sdk package as a dependency for other lfai components - docker build --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-sdk:${LOCAL_VERSION} -f src/leapfrogai_sdk/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-sdk:${LOCAL_VERSION} -f src/leapfrogai_sdk/Dockerfile . docker-supabase: ## Build the migration container for this version of the supabase package - docker build -t ghcr.io/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=packages/supabase/migrations" . + docker build ${DOCKER_FLAGS} -t ghcr.io/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=packages/supabase/migrations" . docker tag ghcr.io/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} build-supabase: local-registry docker-supabase - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/supabase-migrations:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/supabase -a ${ARCH} -o packages/supabase --registry-override=ghcr.io=localhost:${REG_PORT} --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/supabase -a ${ARCH} -o packages/supabase --registry-override=ghcr.io=localhost:${REG_PORT} --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-api: local-registry sdk-wheel + @echo $(DOCKER_FLAGS) + @echo $(ZARF_FLAGS) ## Build the API image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} -f packages/api/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} -f packages/api/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} ## Build the migration container for this version of the API - docker build --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=packages/api/supabase/migrations" . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=packages/api/supabase/migrations" . docker tag ghcr.io/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} build-api: local-registry docker-api ## Build the leapfrogai_api container and Zarf package ## Push the images to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-api:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/api-migrations:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/api -a ${ARCH} -o packages/api --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set LEAPFROGAI_IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/api -a ${ARCH} -o packages/api --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set LEAPFROGAI_IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-ui: ## Build the UI image (and tag it for the local registry) - docker build --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} src/leapfrogai_ui + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} src/leapfrogai_ui docker tag ghcr.io/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} ## Build the migration container for the version of the UI - docker build --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=src/leapfrogai_ui/supabase/migrations" . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} -t ghcr.io/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} -f Dockerfile.migrations --build-arg="MIGRATIONS_DIR=src/leapfrogai_ui/supabase/migrations" . docker tag ghcr.io/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} build-ui: local-registry docker-ui ## Build the leapfrogai_ui container and Zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/leapfrogai-ui:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/ui-migrations:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/ui -a ${ARCH} -o packages/ui --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/ui -a ${ARCH} -o packages/ui --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-llama-cpp-python: sdk-wheel ## Build the image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} -f packages/llama-cpp-python/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} -f packages/llama-cpp-python/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} build-llama-cpp-python: local-registry docker-llama-cpp-python ## Build the llama-cpp-python (cpu) container and Zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/llama-cpp-python:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/llama-cpp-python -a ${ARCH} -o packages/llama-cpp-python --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/llama-cpp-python -a ${ARCH} -o packages/llama-cpp-python --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-vllm: sdk-wheel ## Build the image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} -f packages/vllm/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} -f packages/vllm/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} build-vllm: local-registry docker-vllm ## Build the vllm container and Zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/vllm:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/vllm -a ${ARCH} -o packages/vllm --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/vllm -a ${ARCH} -o packages/vllm --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-text-embeddings: sdk-wheel ## Build the image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} -f packages/text-embeddings/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} -f packages/text-embeddings/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} build-text-embeddings: local-registry docker-text-embeddings ## Build the text-embeddings container and Zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/text-embeddings:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/text-embeddings -a ${ARCH} -o packages/text-embeddings --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/text-embeddings -a ${ARCH} -o packages/text-embeddings --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-whisper: sdk-wheel ## Build the image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} -f packages/whisper/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} -f packages/whisper/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} build-whisper: local-registry docker-whisper ## Build the whisper container and zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/whisper:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/whisper -a ${ARCH} -o packages/whisper --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/whisper -a ${ARCH} -o packages/whisper --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm docker-repeater: sdk-wheel ## Build the image (and tag it for the local registry) - docker build --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} -f packages/repeater/Dockerfile . + docker build ${DOCKER_FLAGS} --platform=linux/${ARCH} --build-arg LOCAL_VERSION=${LOCAL_VERSION} -t ghcr.io/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} -f packages/repeater/Dockerfile . docker tag ghcr.io/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} localhost:${REG_PORT}/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} build-repeater: local-registry docker-repeater ## Build the repeater container and zarf package ## Push the image to the local registry (Zarf is super slow if the image is only in the local daemon) - docker push localhost:${REG_PORT}/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} + docker push ${DOCKER_FLAGS} localhost:${REG_PORT}/defenseunicorns/leapfrogai/repeater:${LOCAL_VERSION} ## Build the Zarf package - uds zarf package create packages/repeater -a ${ARCH} -o packages/repeater --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} --confirm + uds zarf package create packages/repeater -a ${ARCH} -o packages/repeater --registry-override=ghcr.io=localhost:${REG_PORT} --insecure --set IMAGE_VERSION=${LOCAL_VERSION} ${ZARF_FLAGS} --confirm build-cpu: build-supabase build-api build-ui build-llama-cpp-python build-text-embeddings build-whisper ## Build all zarf packages for a cpu-enabled deployment of LFAI @@ -163,3 +167,170 @@ build-all: build-cpu build-gpu ## Build all of the LFAI packages include tests/make-tests.mk include packages/k3d-gpu/Makefile + +silent-build-api-parallel: + @echo "API build started" + @mkdir -p .logs + @$(MAKE) build-api DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-api.log 2>&1 + @echo "API build completed" + +silent-build-supabase-parallel: + @echo "Supabase build started" + @mkdir -p .logs + @$(MAKE) build-supabase DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-supabase.log 2>&1 + @echo "Supabase build completed" + +silent-build-ui-parallel: + @echo "UI build started" + @mkdir -p .logs + @$(MAKE) build-ui DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-ui.log 2>&1 + @echo "UI build completed" + +silent-build-vllm-parallel: + @echo "VLLM build started" + @mkdir -p .logs + @$(MAKE) build-vllm DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-vllm.log 2>&1 + @echo "VLLM build completed" + +silent-build-llama-cpp-python-parallel: + @echo "llama-cpp-python build started" + @mkdir -p .logs + @$(MAKE) build-llama-cpp-python DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-llama-cpp-python.log 2>&1 + @echo "llama-cpp-python build completed" + +silent-build-text-embeddings-parallel: + @echo "text-embeddings build started" + @mkdir -p .logs + @$(MAKE) build-text-embeddings DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-text-embeddings.log 2>&1 + @echo "text-embeddings build completed" + +silent-build-whisper-parallel: + @echo "whisper build started" + @mkdir -p .logs + @$(MAKE) build-whisper DOCKER_FLAGS="$(DOCKER_FLAGS) $(SILENT_DOCKER_FLAGS)" ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" > .logs/build-whisper.log 2>&1 + @echo "whisper build completed" + +silent-build-all: + @echo "Starting parallel builds..." + @echo "Logs at .logs/*.log" + @mkdir -p .logs + @$(MAKE) -j${MAX_JOBS} silent-build-api-parallel silent-build-supabase-parallel silent-build-ui-parallel silent-build-vllm-parallel silent-build-llama-cpp-python-parallel silent-build-text-embeddings-parallel silent-build-whisper-parallel + @echo "All builds completed" + +silent-build-gpu: + @echo "Starting parallel builds..." + @echo "Logs at .logs/*.log" + @mkdir -p .logs + @$(MAKE) -j${MAX_JOBS} silent-build-api-parallel silent-build-supabase-parallel silent-build-ui-parallel silent-build-vllm-parallel silent-build-text-embeddings-parallel silent-build-whisper-parallel + @echo "All builds completed" + +silent-build-cpu: + @echo "Starting parallel builds..." + @echo "Logs at .logs/*.log" + @mkdir -p .logs + @$(MAKE) -j${MAX_JOBS} silent-build-api-parallel silent-build-supabase-parallel silent-build-ui-parallel silent-build-llama-cpp-python-parallel silent-build-text-embeddings-parallel silent-build-whisper-parallel + @echo "All builds completed" + +# Define individual deployment targets +silent-deploy-supabase-package: + @echo "Starting Supabase deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/supabase/zarf-package-supabase-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-supabase.log 2>&1 + @echo "Supabase deployment completed" + +silent-deploy-api-package: + @echo "Starting API deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/api/zarf-package-leapfrogai-api-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-api.log 2>&1 + @echo "API deployment completed" + +silent-deploy-ui-package: + @echo "Starting UI deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/ui/zarf-package-leapfrogai-ui-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-ui.log 2>&1 + @echo "UI deployment completed" + +silent-deploy-llama-cpp-python-package: + @echo "Starting llama-cpp-python deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/llama-cpp-python/zarf-package-llama-cpp-python-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-llama-cpp-python.log 2>&1 + @echo "llama-cpp-python deployment completed" + +silent-deploy-vllm-package: + @echo "Starting VLLM deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/vllm/zarf-package-vllm-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-vllm.log 2>&1 + @echo "VLLM deployment completed" + +silent-deploy-text-embeddings-package: + @echo "Starting text-embeddings deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-text-embeddings.log 2>&1 + @echo "text-embeddings deployment completed" + +silent-deploy-whisper-package: + @echo "Starting whisper deployment..." + @mkdir -p .logs + @uds zarf package deploy packages/whisper/zarf-package-whisper-${ARCH}-${LOCAL_VERSION}.tar.zst ${ZARF_FLAGS} --confirm > .logs/deploy-whisper.log 2>&1 + @echo "whisper deployment completed" + +silent-deploy-cpu: + @echo "Logs at .logs/*.log" + @echo "Starting parallel deployments..." + @echo "Deploying Supabase first to avoid migration issues." + @$(MAKE) silent-deploy-supabase-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" + @echo "Deploying the rest of the packages..." + @$(MAKE) -j${MAX_JOBS} \ + silent-deploy-api-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" \ + silent-deploy-ui-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" \ + silent-deploy-llama-cpp-python-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" \ + silent-deploy-text-embeddings-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" \ + silent-deploy-whisper-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" + @echo "All deployments completed" + +silent-deploy-gpu: + @echo "Logs at .logs/*.log" + @echo "Starting parallel deployments..." + @echo "Deploying Supabase first to avoid migration issues." + @$(MAKE) silent-deploy-supabase-package ZARF_FLAGS="$(ZARF_FLAGS) $(SILENT_ZARF_FLAGS)" + @echo "Deploying API and models..." + @$(MAKE) -j${MAX_JOBS} \ + silent-deploy-api-package ZARF_FLAGS="${ZARF_FLAGS} ${SILENT_ZARF_FLAGS}" \ + silent-deploy-vllm-package ZARF_FLAGS="${ZARF_FLAGS} ${SILENT_ZARF_FLAGS}" \ + silent-deploy-text-embeddings-package ZARF_FLAGS="${ZARF_FLAGS} ${SILENT_ZARF_FLAGS} --set=GPU_CLASS_NAME='nvidia'" \ + silent-deploy-whisper-package ZARF_FLAGS="${ZARF_FLAGS} ${SILENT_ZARF_FLAGS} --set=GPU_CLASS_NAME='nvidia'" + @echo "Deploying UI..." + @$(MAKE) silent-deploy-ui-package ZARF_FLAGS="${ZARF_FLAGS} ${SILENT_ZARF_FLAGS} --set=MODEL='vllm'" + @echo "All deployments completed" + +silent-fresh-leapfrogai-gpu: + @echo "Cleaning up previous artifacts..." + @$(MAKE) clean > /dev/null 2>&1 + @echo "Logs at .logs/*.log" + @mkdir -p .logs + @echo "Creating a uds gpu enabled cluster..." + @$(MAKE) create-uds-gpu-cluster DOCKER_FLAGS="${SILENT_DOCKER_FLAGS}" ZARF_FLAGS="${SILENT_ZARF_FLAGS}" > .logs/create-uds-gpu-cluster.log 2>&1 + @echo "Testing the uds gpu cluster..." + @$(MAKE) test-uds-gpu-cluster > .logs/test-uds-gpu-cluster.log 2>&1 + @echo "Building all packages..." + @$(MAKE) silent-build-gpu + @echo "Deploying all packages..." + @$(MAKE) silent-deploy-gpu + @echo "Done!" + @echo "UI is available at https://ai.uds.dev" + @echo "API is available at https://leapfrogai-api.uds.dev" + +silent-fresh-leapfrogai-cpu: + @echo "Cleaning up previous artifacts..." + @$(MAKE) clean > /dev/null 2>&1 + @echo "Logs at .logs/*.log" + @mkdir -p .logs + @echo "Creating a uds cpu-only cluster..." + @$(MAKE) create-uds-cpu-cluster DOCKER_FLAGS="${SILENT_DOCKER_FLAGS}" ZARF_FLAGS="${SILENT_ZARF_FLAGS}" > .logs/create-uds-cpu-cluster.log 2>&1 + @echo "Building all packages..." + @$(MAKE) silent-build-cpu + @echo "Deploying all packages..." + @$(MAKE) silent-deploy-cpu + @echo "Done!" + @echo "UI is available at https://ai.uds.dev" + @echo "API is available at https://leapfrogai-api.uds.dev" diff --git a/README.md b/README.md index fb4d8c464..92a1b8af6 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ LeapfrogAI can be deployed and run locally via UDS and Kubernetes, built out usi Prior to deploying any LeapfrogAI packages, a UDS Kubernetes cluster must be deployed using the most recent k3d bundle: ```sh -uds deploy k3d-core-slim-dev:0.23.0 +make create-uds-cpu-cluster ``` #### UDS Latest diff --git a/adr/0003-database.md b/adr/0003-database.md index 512170dce..b5ef6ad94 100644 --- a/adr/0003-database.md +++ b/adr/0003-database.md @@ -14,7 +14,7 @@ ## Status -PROPOSED +ACCEPTED ## Context diff --git a/adr/0006-queueing-high-traffic.md b/adr/0006-queueing-high-traffic.md new file mode 100644 index 000000000..70502a799 --- /dev/null +++ b/adr/0006-queueing-high-traffic.md @@ -0,0 +1,100 @@ +# Queueing and High Traffic + +## Table of Contents + +- [Handling High Traffic](#Queueing-and-High-Traffic) + - [Table of Contents](#table-of-contents) + - [Status](#status) + - [Background](#background) + - [Decision](#decision) + - [Rationale](#rationale) + - [Alternatives](#alternatives) + - [Related ADRs](#related-adrs) + - [References](#references) + +## Status + +PROPOSED + +## Context + +LeapfrogAI needs to handle a large volume of inference, file upload, and embeddings requests. To ensure that we can manage this level of activity without significant performance degradation, we need to implement systems that prevent overwhelming or blocking by a large volume or single long-running task. + +Adding a Queue management component can help create a more efficient request management system to deal with high request volumes of long-running tasks. However, it may introduce a significant level of complexity to the system, and we must weigh the options carefully. + +Benefits of a Queue system for request processing: +- Allows the API to quickly respond even when the system is very busy. +- Prevents requests from being dropped or timing out. +- Allows resuming failed requests. +- Enables throttling of message processing rate. + +## Decision + +We have decided to implement a multi-tiered approach to address the queueing and high traffic challenges: + +1. Address underlying bottlenecks in the system: + - Optimize endpoint implementations, processing of long-running tasks, and indexing of files. + - Reduce duplication of indexing efforts. + - Scale horizontal/vertical resources as needed. + +2. Implement a lightweight queueing solution using Supabase Realtime and FastAPI background tasks: + - Utilize Supabase Realtime for task status updates (in-progress, complete, etc...) and basic queueing. + - In the event of issues with Supabase Realtime, fallback to RedPanda. + - Leverage FastAPI's background tasks to handle long running operations asynchronously in the background. + +3. Prepare for future scaling by designing the system to easily integrate with a more robust queueing solution: + - Design interfaces that can work with both our current lightweight solution and future, more robust options. + - Do not attempt to push Supabase Realtime beyond its designed limits, instead plan to use RedPanda or RabbitMQ if those needs surface. + +## Rationale +1. Addressing underlying bottlenecks: + - This approach ensures we're not masking performance issues with a queueing system. + - Optimizations can significantly improve system performance without adding complexity. + +2. Lightweight solution (Supabase Realtime and FastAPI background tasks): + - Leverages existing infrastructure (Supabase) reducing additional operational overhead. + - FastAPI background tasks provide a simple way to handle asynchronous operations without introducing new dependencies. + - This solution meets our current needs without over-engineering. + +3. Preparation for future scaling: + - Allows for easy transition to more robust solutions as the system grows. + - Prevents lock-in to a solution that may not meet future needs. + +We chose this approach over alternatives for a few reasons: +- This tiered approach allows us to start with a simple solution while preparing for future growth. +- Some alternatives are viable but would likely require significant additional setup and mx work to bring to the current environment. + - The additional setup includes but is not limited to: new Zarf packages, updates to uds bundles, spikes to integrate with current app, resolving any permissions/hardening issues, more containers to add to ironbank/chainguard. +- When performing load testing on the system, the primary bottlenecks seem to be around the vectordb file indexing. + - The issues related to this process should be able to be resolved by optimizations, a light amount of queueing, and background tasks. + - Issues not related to indexing were primarily scalability issues. Which can be resolved via resource limits, throttling, improving horizontal and vertical scaling within the cluster. +- Authentication will be an issue for every solution except Supabase Realtime. + +## Alternatives +Queueing Solutions Considered: +* RabbitMQ: Meets current and future needs. + * Well maintained JS and Python libraries. + * Requires additional, potentially significant integration work to bring into the k8s cluster. +* Supabase Realtime: Lightweight and already integrated, but may not meet all future queuing needs. + * Well maintained JS and Python libraries. + * Can listen directly to db transactions. + * Already integrated with Supabase auth. +* Kafka: Powerful but too heavy for our current requirements. + * Well maintained JS and Python libraries. + * Requires additional, potentially significant integration work to bring into the k8s cluster. +* Celery: Good option for Python-based systems, but introduces additional dependencies. + * Python library well maintained. JS library not well maintained. +* RedPanda: Accessible internally and provides a scalable solution. + * Well maintained JS and Python libraries as it supports the same tooling as Kafka. + * Zarf/UDS bundle already available. +* Custom Python solution: Flexible but requires significant unnecessary development effort given the tools already available. + +## Related ADRs +* [0003-database](0003-database.md) + +## References +1. Supabase Realtime Documentation: https://supabase.com/docs/guides/realtime +2. FastAPI Background Tasks: https://fastapi.tiangolo.com/tutorial/background-tasks/ +3. Celery Documentation: https://docs.celeryq.dev/en/stable/ +4. Kafka Documentation: https://kafka.apache.org/ +5. RabbitMQ Documentation: https://www.rabbitmq.com/docs +6. RedPanda: https://docs.redpanda.com/docs/ \ No newline at end of file diff --git a/packages/api/chart/Chart.yaml b/packages/api/chart/Chart.yaml index 9b8d17b19..744281aff 100644 --- a/packages/api/chart/Chart.yaml +++ b/packages/api/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/api/chart/values.yaml b/packages/api/chart/values.yaml index ea69fdf6e..d96db89fd 100644 --- a/packages/api/chart/values.yaml +++ b/packages/api/chart/values.yaml @@ -1,6 +1,6 @@ image: # x-release-please-start-version - lfaiAPITag: 0.9.2 + lfaiAPITag: 0.10.0 # x-release-please-end kiwigridTag: 1.23.3 diff --git a/packages/api/supabase/migrations/20240729193626_v0.10.0_api_keys_storage_objects.sql b/packages/api/supabase/migrations/20240729193626_v0.10.0_api_keys_storage_objects.sql new file mode 100644 index 000000000..562669c5d --- /dev/null +++ b/packages/api/supabase/migrations/20240729193626_v0.10.0_api_keys_storage_objects.sql @@ -0,0 +1,11 @@ +create policy "Individuals can CRUD storage.objects via API key." + on storage.objects for all + to anon + using + ( + exists ( + select 1 + from api_keys + where api_keys.api_key_hash = crypt(current_setting('request.headers')::json->>'x-custom-api-key', api_keys.api_key_hash) + ) + ); diff --git a/packages/api/zarf.yaml b/packages/api/zarf.yaml index 2b22a3f18..e7bb8e76f 100644 --- a/packages/api/zarf.yaml +++ b/packages/api/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: @@ -28,7 +28,7 @@ components: namespace: leapfrogai localPath: chart # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "lfai-values.yaml" diff --git a/packages/k3d-gpu/Makefile b/packages/k3d-gpu/Makefile index 7dfc7e5e9..4193e6d0f 100644 --- a/packages/k3d-gpu/Makefile +++ b/packages/k3d-gpu/Makefile @@ -1,20 +1,28 @@ MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) -UDS_VERSION := 0.24.1 +UDS_VERSION := 0.25.0 LOCAL_VERSION ?= $(shell git rev-parse --short HEAD) - +DOCKER_FLAGS := +ZARF_FLAGS := build-k3d-gpu: @cd ${MAKEFILE_DIR} && \ docker build \ + ${DOCKER_FLAGS} \ --platform linux/amd64 \ -t ghcr.io/defenseunicorns/leapfrogai/k3d-gpu:${LOCAL_VERSION} . create-uds-gpu-cluster: build-k3d-gpu @uds deploy k3d-core-slim-dev:${UDS_VERSION} \ + ${ZARF_FLAGS} \ --set K3D_EXTRA_ARGS="--gpus=all \ --image=ghcr.io/defenseunicorns/leapfrogai/k3d-gpu:${LOCAL_VERSION}" --confirm +create-uds-cpu-cluster: build-k3d-gpu + @uds deploy k3d-core-slim-dev:${UDS_VERSION} \ + ${ZARF_FLAGS} \ + --confirm + test-uds-gpu-cluster: @cd ${MAKEFILE_DIR} && \ uds zarf tools kubectl apply -f ./test/cuda-vector-add.yaml @@ -23,4 +31,4 @@ test-uds-gpu-cluster: @cd ${MAKEFILE_DIR} && \ uds zarf tools kubectl delete -f ./test/cuda-vector-add.yaml -.PHONY: build-k3d-gpu create-uds-gpu-cluster test-uds-gpu-cluster +.PHONY: build-k3d-gpu create-uds-gpu-cluster create-uds-cpu-cluster test-uds-gpu-cluster diff --git a/packages/k3d-gpu/plugin/device-plugin-daemonset.yaml b/packages/k3d-gpu/plugin/device-plugin-daemonset.yaml index 202280341..4f8dd882a 100644 --- a/packages/k3d-gpu/plugin/device-plugin-daemonset.yaml +++ b/packages/k3d-gpu/plugin/device-plugin-daemonset.yaml @@ -45,7 +45,7 @@ spec: - name: NVIDIA_VISIBLE_DEVICES value: all - name: NVIDIA_DRIVER_CAPABILITIES - value: all + value: compute,utility - name: MPS_ROOT value: /run/nvidia/mps securityContext: diff --git a/packages/llama-cpp-python/Dockerfile b/packages/llama-cpp-python/Dockerfile index 63d5c09aa..b7041bed9 100644 --- a/packages/llama-cpp-python/Dockerfile +++ b/packages/llama-cpp-python/Dockerfile @@ -7,19 +7,6 @@ ARG SDK_DEST=src/leapfrogai_sdk/build USER root WORKDIR /leapfrogai -# download model -RUN python -m pip install -U huggingface_hub[cli,hf_transfer] -ARG REPO_ID=TheBloke/SynthIA-7B-v2.0-GGUF -ARG FILENAME=synthia-7b-v2.0.Q4_K_M.gguf -ARG REVISION=3f65d882253d1f15a113dabf473a7c02a004d2b5 - -# NOTE: This is checking for a pre-downloaded model file in the local build dir before downloading the model from HuggingFace -# TODO: Add checksum validation to verify the model in the local build-dir is the model we expect -COPY packages/llama-cpp-python/scripts/model_download.py scripts/model_download.py -RUN REPO_ID=${REPO_ID} FILENAME=${FILENAME} REVISION=${REVISION} python3.11 scripts/model_download.py -RUN mv .model/*.gguf .model/model.gguf - - # create virtual environment for light-weight portability and minimal libraries RUN python3.11 -m venv .venv ENV PATH="/leapfrogai/.venv/bin:$PATH" @@ -42,7 +29,6 @@ ENV PATH="/leapfrogai/.venv/bin:$PATH" WORKDIR /leapfrogai COPY --from=builder /leapfrogai/.venv/ /leapfrogai/.venv/ -COPY --from=builder /leapfrogai/.model/ /leapfrogai/.model/ COPY packages/llama-cpp-python/main.py . COPY packages/llama-cpp-python/config.yaml . diff --git a/packages/llama-cpp-python/README.md b/packages/llama-cpp-python/README.md index 313649ed3..9aed7f7c7 100644 --- a/packages/llama-cpp-python/README.md +++ b/packages/llama-cpp-python/README.md @@ -37,6 +37,7 @@ To build and deploy just the llama-cpp-python Zarf package (from the root of the > Deploy a [UDS cluster](/README.md#uds) if one isn't deployed already ```shell +pip install 'huggingface_hub[cli,hf_transfer]' # Used to download the model weights from huggingface make build-llama-cpp-python LOCAL_VERSION=dev uds zarf package deploy packages/llama-cpp-python/zarf-package-llama-cpp-python-*-dev.tar.zst --confirm ``` diff --git a/packages/llama-cpp-python/chart/Chart.yaml b/packages/llama-cpp-python/chart/Chart.yaml index c89277682..0eddf27e6 100644 --- a/packages/llama-cpp-python/chart/Chart.yaml +++ b/packages/llama-cpp-python/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/llama-cpp-python/chart/templates/deployment.yaml b/packages/llama-cpp-python/chart/templates/deployment.yaml index 8d014f1c7..67082c4f4 100644 --- a/packages/llama-cpp-python/chart/templates/deployment.yaml +++ b/packages/llama-cpp-python/chart/templates/deployment.yaml @@ -23,8 +23,38 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: + app: lfai-llama {{- include "chart.selectorLabels" . | nindent 8 }} spec: + # It's necessary to include the ###ZARF_DATA_INJECTION_MARKER### somewhere in the podspec, otherwise data injections will not occur. + initContainers: + - name: data-loader + image: cgr.dev/chainguard/bash:latest + securityContext: + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + # This command looks for the Zarf "data injection marker" which is a timestamped file that is injected after everything else and marks the injection as complete. + command: + [ + "sh", + "-c", + 'while [ ! -f /data/.model/###ZARF_DATA_INJECTION_MARKER### ]; do echo "waiting for zarf data sync" && sleep 1; done; echo "we are done waiting!"', + ] + resources: + requests: + memory: "64Mi" + cpu: "200m" + limits: + memory: "128Mi" + cpu: "500m" + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: /data + volumes: + - name: leapfrogai-pv-storage + persistentVolumeClaim: + claimName: lfai-llama-pv-claim securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -39,6 +69,9 @@ spec: protocol: TCP resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: "/data" {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/packages/llama-cpp-python/chart/templates/pvc.yaml b/packages/llama-cpp-python/chart/templates/pvc.yaml new file mode 100644 index 000000000..2b161cffc --- /dev/null +++ b/packages/llama-cpp-python/chart/templates/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: lfai-llama-pv-claim + namespace: leapfrogai +spec: + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + accessModes: + - {{ .Values.persistence.accessModes | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} diff --git a/packages/llama-cpp-python/chart/values.yaml b/packages/llama-cpp-python/chart/values.yaml index 5fee08d11..990341c52 100644 --- a/packages/llama-cpp-python/chart/values.yaml +++ b/packages/llama-cpp-python/chart/values.yaml @@ -9,7 +9,7 @@ image: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. # x-release-please-start-version - tag: 0.9.2 + tag: 0.10.0 # x-release-please-end nameOverride: llama-cpp-python @@ -54,3 +54,8 @@ nodeSelector: {} tolerations: [] affinity: {} + +persistence: + size: 15Gi + accessModes: ReadWriteOnce + storageClass: "local-path" diff --git a/packages/llama-cpp-python/config.yaml b/packages/llama-cpp-python/config.yaml index 30bf38126..4b307bca6 100644 --- a/packages/llama-cpp-python/config.yaml +++ b/packages/llama-cpp-python/config.yaml @@ -1,5 +1,5 @@ model: - source: ".model/model.gguf" + source: "/data/.model/model.gguf" max_context_length: 16384 stop_tokens: - "<|im_end|>" diff --git a/packages/llama-cpp-python/llama-cpp-python-values.yaml b/packages/llama-cpp-python/llama-cpp-python-values.yaml index 3e3c22379..f2acd8b4b 100644 --- a/packages/llama-cpp-python/llama-cpp-python-values.yaml +++ b/packages/llama-cpp-python/llama-cpp-python-values.yaml @@ -1,2 +1,7 @@ image: tag: "###ZARF_CONST_IMAGE_VERSION###" + +persistence: + size: ###ZARF_VAR_PVC_SIZE### + accessModes: ###ZARF_VAR_PVC_ACCESS_MODE### + storageClass: ###ZARF_VAR_PVC_STORAGE_CLASS### diff --git a/packages/llama-cpp-python/main.py b/packages/llama-cpp-python/main.py index 12f10460c..70b01cc66 100644 --- a/packages/llama-cpp-python/main.py +++ b/packages/llama-cpp-python/main.py @@ -15,6 +15,9 @@ class Model: backend_config = BackendConfig() + if not os.path.exists(backend_config.model.source): + raise ValueError(f"Model path ({backend_config.model.source}) does not exist") + llm = Llama( model_path=backend_config.model.source, n_ctx=backend_config.max_context_length, diff --git a/packages/llama-cpp-python/pyproject.toml b/packages/llama-cpp-python/pyproject.toml index 9874a09d7..c47c875f4 100644 --- a/packages/llama-cpp-python/pyproject.toml +++ b/packages/llama-cpp-python/pyproject.toml @@ -3,7 +3,7 @@ name = "lfai-llama-cpp-python" description = "A LeapfrogAI API-compatible llama-cpp-python wrapper for quantized and un-quantized model inferencing on CPU infrastructures." # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ diff --git a/packages/llama-cpp-python/scripts/model_download.py b/packages/llama-cpp-python/scripts/model_download.py index 7a4188942..a5cc79e52 100644 --- a/packages/llama-cpp-python/scripts/model_download.py +++ b/packages/llama-cpp-python/scripts/model_download.py @@ -1,17 +1,34 @@ import os +import hashlib +import urllib.request -from huggingface_hub import hf_hub_download +REPO_ID = os.environ.get("REPO_ID", "") +FILENAME = os.environ.get("FILENAME", "") +REVISION = os.environ.get("REVISION", "main") +CHECKSUM = os.environ.get("SHA256_CHECKSUM", "") +OUTPUT_FILE = os.environ.get("OUTPUT_FILE", ".model/model.gguf") -REPO_ID = os.environ.get("REPO_ID", "TheBloke/SynthIA-7B-v2.0-GGUF") -FILENAME = os.environ.get("FILENAME", "synthia-7b-v2.0.Q4_K_M.gguf") -REVISION = os.environ.get("REVISION", "3f65d882253d1f15a113dabf473a7c02a004d2b5") -os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1" +def download_model(): + # Check if the model is already downloaded. + if os.path.exists(OUTPUT_FILE) and CHECKSUM != "": + if hashlib.sha256(open(OUTPUT_FILE, "rb").read()).hexdigest() == CHECKSUM: + print("Model already downloaded.") + return -hf_hub_download( - repo_id=REPO_ID, - filename=FILENAME, - local_dir=".model", - local_dir_use_symlinks=False, - revision=REVISION, -) + # Validate that require environment variables are provided + if REPO_ID == "" or FILENAME == "": + print("Please provide REPO_ID and FILENAME environment variables.") + return + + # Download the model! + print("Downloading model... This may take a while.") + if not os.path.exists(".model"): + os.mkdir(".model") + urllib.request.urlretrieve( + f"https://huggingface.co/{REPO_ID}/resolve/{REVISION}/{FILENAME}", OUTPUT_FILE + ) + + +if __name__ == "__main__": + download_model() diff --git a/packages/llama-cpp-python/zarf.yaml b/packages/llama-cpp-python/zarf.yaml index 8f0510eb5..0bdc4fe72 100644 --- a/packages/llama-cpp-python/zarf.yaml +++ b/packages/llama-cpp-python/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: @@ -11,6 +11,19 @@ constants: - name: IMAGE_VERSION value: "###ZARF_PKG_TMPL_IMAGE_VERSION###" +variables: + - name: PVC_SIZE + description: Size of the PVC used for model storage. + default: "15Gi" + pattern: "^[0-9]+[a-zA-Z]+$" + - name: PVC_ACCESS_MODE + description: Access mode of the PVC used for model storage. + default: "ReadWriteOnce" + pattern: "^(ReadWriteOnce|ReadOnlyMany|ReadWriteMany)$" + - name: PVC_STORAGE_CLASS + description: Storage class of the PVC used for model storage. + default: "local-path" + components: - name: llama-cpp-python-model required: true @@ -20,9 +33,28 @@ components: localPath: chart releaseName: llama-cpp-python-model # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "llama-cpp-python-values.yaml" images: - ghcr.io/defenseunicorns/leapfrogai/llama-cpp-python:###ZARF_PKG_TMPL_IMAGE_VERSION### + - cgr.dev/chainguard/bash:latest + dataInjections: + - source: .model/ + target: + namespace: leapfrogai + selector: app=lfai-llama + container: data-loader + path: /data/.model + compress: true + actions: + onCreate: + before: + # NOTE: This assumes python is installed and in $PATH and 'huggingface_hub[cli,hf_transfer]' has been installed + - cmd: python scripts/model_download.py + env: + - REPO_ID=TheBloke/SynthIA-7B-v2.0-GGUF + - FILENAME=synthia-7b-v2.0.Q4_K_M.gguf + - REVISION=3f65d882253d1f15a113dabf473a7c02a004d2b5 + - SHA256_CHECKSUM=5d6369d456446c40a9fd149525747d8dc494196686861c43b00f9230a166ba82 diff --git a/packages/repeater/chart/Chart.yaml b/packages/repeater/chart/Chart.yaml index 2b62387ee..906274ace 100644 --- a/packages/repeater/chart/Chart.yaml +++ b/packages/repeater/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/repeater/chart/values.yaml b/packages/repeater/chart/values.yaml index c1cb3827f..84db4f3df 100644 --- a/packages/repeater/chart/values.yaml +++ b/packages/repeater/chart/values.yaml @@ -9,7 +9,7 @@ image: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. # x-release-please-start-version - tag: 0.9.2 + tag: 0.10.0 # x-release-please-end nameOverride: repeater diff --git a/packages/repeater/pyproject.toml b/packages/repeater/pyproject.toml index b77c92cb6..c7a562f97 100644 --- a/packages/repeater/pyproject.toml +++ b/packages/repeater/pyproject.toml @@ -3,7 +3,7 @@ name = "lfai-repeater" description = "A LeapfrogAI API-compatible pseudo-model for testing the API." # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ diff --git a/packages/repeater/zarf-config.yaml b/packages/repeater/zarf-config.yaml index 7d41ed468..6b074b8c9 100644 --- a/packages/repeater/zarf-config.yaml +++ b/packages/repeater/zarf-config.yaml @@ -3,7 +3,7 @@ package: set: image_repository: "ghcr.io/defenseunicorns/leapfrogai/repeater" # x-release-please-start-version - image_version: 0.9.2 + image_version: 0.10.0 # x-release-please-end name: repeater max_package_size: "1000000000" diff --git a/packages/repeater/zarf.yaml b/packages/repeater/zarf.yaml index e17de5bf7..84373d464 100644 --- a/packages/repeater/zarf.yaml +++ b/packages/repeater/zarf.yaml @@ -18,7 +18,7 @@ components: localPath: chart releaseName: repeater # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "repeater-values.yaml" diff --git a/packages/supabase/bitnami-values.yaml b/packages/supabase/bitnami-values.yaml index 3f9dceb8b..2ae319903 100644 --- a/packages/supabase/bitnami-values.yaml +++ b/packages/supabase/bitnami-values.yaml @@ -75,6 +75,22 @@ realtime: resourcesPreset: "none" podLabels: sidecar.istio.io/inject: "false" + extraEnvVars: + - name: APP_NAME + value: "supabase-realtime" + - name: DB_AFTER_CONNECT_QUERY + value: "DO $body$ BEGIN CREATE SCHEMA IF NOT EXISTS _realtime; ALTER SCHEMA _realtime OWNER TO postgres; SET search_path TO _realtime; END $body$;" + - name: DB_ENC_KEY + valueFrom: + secretKeyRef: + name: supabase-realtime-extra + key: dbEncKey + - name: DNS_NODES + value: "supabase-realtime" + args: + - -ec + - | + realtime eval Realtime.Release.migrate && realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && realtime start rest: enabled: ###ZARF_VAR_ENABLE_REST### @@ -107,7 +123,7 @@ volumePermissions: resourcesPreset: "none" psqlImage: - tag: 15.1.1-debian-12-r69 + tag: 15.6.1-debian-12-r2 kong: enabled: ###ZARF_VAR_ENABLE_KONG### @@ -167,12 +183,16 @@ kong: timeoutSeconds: 40 readinessProbe: timeoutSeconds: 40 + service: + type: ClusterIP postgresql: enabled: ###ZARF_VAR_ENABLE_POSTGRES### image: - tag: 15.1.1-debian-12-r69 + tag: 15.6.1-debian-12-r2 debug: true primary: + extendedConfiguration: | + wal_level = logical resourcesPreset: "none" podLabels: sidecar.istio.io/inject: "false" @@ -182,4 +202,4 @@ postgresql: ## @param postgresql.postgresqlSharedPreloadLibraries Set the shared_preload_libraries parameter in postgresql.conf ## Setting an empty value in order to force the default extensions of supabase-postgres ## - postgresqlSharedPreloadLibraries: "pg_stat_statements, pg_stat_monitor, pgaudit, plpgsql, plpgsql_check, pg_cron, pg_net, pgsodium, timescaledb, auto_explain, vector" + postgresqlSharedPreloadLibraries: "pg_stat_statements, pg_stat_monitor, pgaudit, plpgsql, plpgsql_check, pg_cron, pg_net, pgsodium, timescaledb, auto_explain, vector" \ No newline at end of file diff --git a/packages/supabase/chart/templates/suapbase-realtime-secret.yaml b/packages/supabase/chart/templates/suapbase-realtime-secret.yaml new file mode 100644 index 000000000..a762df845 --- /dev/null +++ b/packages/supabase/chart/templates/suapbase-realtime-secret.yaml @@ -0,0 +1,18 @@ +{{- $dbEncKey := randAlphaNum 16 }} # This needs to be exactly 16 characters +{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "supabase-realtime-extra") }} +apiVersion: v1 +kind: Secret +metadata: + name: supabase-realtime-extra + namespace: {{ .Release.Namespace }} + {{- if $existingSecret }} + annotations: + "helm.sh/resource-policy": keep + {{- end }} +type: Opaque +data: + {{- if $existingSecret }} + dbEncKey: {{ $existingSecret.data.dbEncKey }} + {{- else }} + dbEncKey: {{ $dbEncKey | b64enc | quote }} + {{- end }} \ No newline at end of file diff --git a/packages/supabase/manifests/declarative-conf-configmap.yaml b/packages/supabase/manifests/declarative-conf-configmap.yaml index 8a4620f2c..344ec2f48 100644 --- a/packages/supabase/manifests/declarative-conf-configmap.yaml +++ b/packages/supabase/manifests/declarative-conf-configmap.yaml @@ -95,11 +95,31 @@ data: - admin - anon - - name: realtime-v1 - _comment: "Realtime: /realtime/v1/* -> ws://supabase-realtime:80/socket/*" - url: http://supabase-realtime:80/socket + - name: realtime-v1-ws + _comment: "Realtime: /realtime/v1/* -> ws://supabase-realtime:80/socket/websocket/*" + url: http://supabase-realtime:80/socket/websocket routes: - - name: realtime-v1-all + - name: realtime-v1-ws + strip_path: true + paths: + - /realtime/v1/websocket + plugins: + - name: cors + - name: key-auth + config: + hide_credentials: false + - name: acl + config: + hide_groups_header: true + allow: + - admin + - anon + + - name: realtime-v1-api + _comment: "Realtime: /realtime/v1/* -> http://supabase-realtime:80/*" + url: http://supabase-realtime:80 + routes: + - name: realtime-v1-api strip_path: true paths: - /realtime/v1/ diff --git a/packages/supabase/migrations/20240808093300_v0.10.0_realtime_tenant.sql b/packages/supabase/migrations/20240808093300_v0.10.0_realtime_tenant.sql new file mode 100644 index 000000000..27f58c83d --- /dev/null +++ b/packages/supabase/migrations/20240808093300_v0.10.0_realtime_tenant.sql @@ -0,0 +1,22 @@ +-- Disable the foreign key constraint +ALTER TABLE _realtime.extensions +DROP CONSTRAINT extensions_tenant_external_id_fkey; + +-- Update the external_id and name for the realtime tenant +UPDATE _realtime.tenants +SET external_id = 'supabase-realtime', + name = 'supabase-realtime' +WHERE external_id = 'realtime-dev' + AND name = 'realtime-dev'; + +-- Update the tenant_external_id for the realtime extension +UPDATE _realtime.extensions +SET tenant_external_id = 'supabase-realtime' +WHERE tenant_external_id = 'realtime-dev'; + +-- Re-enable the foreign key constraint +ALTER TABLE _realtime.extensions +ADD CONSTRAINT extensions_tenant_external_id_fkey +FOREIGN KEY (tenant_external_id) +REFERENCES _realtime.tenants(external_id) +ON DELETE CASCADE; \ No newline at end of file diff --git a/packages/supabase/zarf.yaml b/packages/supabase/zarf.yaml index 4ad6c0fde..094219fcd 100644 --- a/packages/supabase/zarf.yaml +++ b/packages/supabase/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: name: supabase @@ -66,7 +66,7 @@ components: namespace: leapfrogai url: oci://registry-1.docker.io/bitnamicharts/supabase releaseName: supabase-bootstrap - version: 4.0.5 + version: 5.3.3 valuesFiles: - "bitnami-values.yaml" - "bitnami-values-bootstrap.yaml" @@ -81,21 +81,21 @@ components: namespace: leapfrogai url: oci://registry-1.docker.io/bitnamicharts/supabase releaseName: supabase - version: 4.0.5 + version: 5.3.3 valuesFiles: - "bitnami-values.yaml" images: - - docker.io/bitnami/gotrue:2.150.1-debian-12-r1 - - docker.io/bitnami/jwt-cli:6.0.0-debian-12-r20 - - docker.io/bitnami/kubectl:1.30.0-debian-12-r0 - - docker.io/bitnami/os-shell:12-debian-12-r19 - - docker.io/bitnami/postgrest:11.2.2-debian-12-r15 - - docker.io/bitnami/supabase-postgres:15.1.1-debian-12-r69 - - docker.io/bitnami/supabase-postgres-meta:0.80.0-debian-12-r3 - - docker.io/bitnami/supabase-realtime:2.28.32-debian-12-r2 - - docker.io/bitnami/supabase-storage:0.48.4-debian-12-r2 - - docker.io/bitnami/supabase-studio:0.24.3-debian-12-r3 - - docker.io/bitnami/kong:3.6.1-debian-12-r18 + - docker.io/bitnami/gotrue:2.155.6-debian-12-r3 + - docker.io/bitnami/jwt-cli:6.1.0-debian-12-r5 + - docker.io/bitnami/kubectl:1.30.3-debian-12-r4 + - docker.io/bitnami/os-shell:12-debian-12-r27 + - docker.io/bitnami/postgrest:11.2.2-debian-12-r31 + - docker.io/bitnami/supabase-postgres:15.6.1-debian-12-r2 + - docker.io/bitnami/supabase-postgres-meta:0.83.2-debian-12-r3 + - docker.io/bitnami/supabase-realtime:2.30.14-debian-12-r2 + - docker.io/bitnami/supabase-storage:1.8.2-debian-12-r2 + - docker.io/bitnami/supabase-studio:1.24.5-debian-12-r4 + - docker.io/bitnami/kong:3.7.1-debian-12-r5 - name: supabase-post-process description: "Perform necessary post processing here" required: true diff --git a/packages/text-embeddings/Dockerfile b/packages/text-embeddings/Dockerfile index 9c1e76f5b..9af7c5537 100644 --- a/packages/text-embeddings/Dockerfile +++ b/packages/text-embeddings/Dockerfile @@ -14,7 +14,6 @@ ENV PATH="/leapfrogai/.venv/bin:$PATH" # copy and install all python dependencies # NOTE: We are copying the leapfrog whl to this filename because installing 'optional extras' from # a wheel requires the absolute path to the wheel file (instead of a wildcard whl) - COPY --from=sdk /leapfrogai/${SDK_DEST} ${SDK_DEST} COPY packages/text-embeddings packages/text-embeddings @@ -22,14 +21,6 @@ RUN rm -f packages/text-embeddings/build/*.whl RUN python -m pip wheel packages/text-embeddings -w packages/text-embeddings/build --find-links=${SDK_DEST} RUN pip install packages/text-embeddings/build/lfai_text_embeddings*.whl --no-index --find-links=packages/text-embeddings/build/ - -# download model -RUN python -m pip install -U huggingface_hub[cli,hf_transfer] -ARG REPO_ID="hkunlp/instructor-xl" -ARG REVISION="ce48b213095e647a6c3536364b9fa00daf57f436" -COPY packages/text-embeddings/scripts/model_download.py scripts/model_download.py -RUN REPO_ID=${REPO_ID} REVISION=${REVISION} python scripts/model_download.py - # hardened and slim python image FROM ghcr.io/defenseunicorns/leapfrogai/python:3.11 @@ -38,7 +29,6 @@ ENV PATH="/leapfrogai/.venv/bin:$PATH" WORKDIR /leapfrogai COPY --from=builder /leapfrogai/.venv/ /leapfrogai/.venv/ -COPY --from=builder /leapfrogai/.model/ /leapfrogai/.model/ COPY packages/text-embeddings/main.py . diff --git a/packages/text-embeddings/README.md b/packages/text-embeddings/README.md index 33482867d..09bef42b4 100644 --- a/packages/text-embeddings/README.md +++ b/packages/text-embeddings/README.md @@ -14,6 +14,7 @@ To build and deploy just the text-embeddings Zarf package (from the root of the > Deploy a [UDS cluster](/README.md#uds) if one isn't deployed already ```shell +pip install 'huggingface_hub[cli,hf_transfer]' # Used to download the model weights from huggingface make build-text-embeddings LOCAL_VERSION=dev uds zarf package deploy packages/text-embeddings/zarf-package-text-embeddings-*-dev.tar.zst --confirm ``` diff --git a/packages/text-embeddings/chart/Chart.yaml b/packages/text-embeddings/chart/Chart.yaml index c89277682..0eddf27e6 100644 --- a/packages/text-embeddings/chart/Chart.yaml +++ b/packages/text-embeddings/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/text-embeddings/chart/templates/deployment.yaml b/packages/text-embeddings/chart/templates/deployment.yaml index 8d014f1c7..77d7d5e0b 100644 --- a/packages/text-embeddings/chart/templates/deployment.yaml +++ b/packages/text-embeddings/chart/templates/deployment.yaml @@ -23,8 +23,43 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: + app: lfai-text-embeddings {{- include "chart.selectorLabels" . | nindent 8 }} spec: + {{- if gt (index .Values.resources.limits "nvidia.com/gpu") 0.0 }} + runtimeClassName: nvidia + {{- else if .Values.gpu.runtimeClassName }} + runtimeClassName: {{ .Values.gpu.runtimeClassName }} + {{- end }} + # It's necessary to include the ###ZARF_DATA_INJECTION_MARKER### somewhere in the podspec, otherwise data injections will not occur. + initContainers: + - name: data-loader + image: cgr.dev/chainguard/bash:latest + securityContext: + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + # This command looks for the Zarf "data injection marker" which is a timestamped file that is injected after everything else and marks the injection as complete. + command: + [ + "sh", + "-c", + 'while [ ! -f /data/.model/###ZARF_DATA_INJECTION_MARKER### ]; do echo "waiting for zarf data sync" && sleep 1; done; echo "we are done waiting!"', + ] + resources: + requests: + memory: "64Mi" + cpu: "200m" + limits: + memory: "128Mi" + cpu: "500m" + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: /data + volumes: + - name: leapfrogai-pv-storage + persistentVolumeClaim: + claimName: lfai-text-embeddings-pv-claim securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -39,6 +74,12 @@ spec: protocol: TCP resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: "/data" + env: + - name: LFAI_MODEL_PATH + value: '/data/.model' {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/packages/text-embeddings/chart/templates/pvc.yaml b/packages/text-embeddings/chart/templates/pvc.yaml new file mode 100644 index 000000000..3ce063f32 --- /dev/null +++ b/packages/text-embeddings/chart/templates/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: lfai-text-embeddings-pv-claim + namespace: leapfrogai +spec: + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + accessModes: + - {{ .Values.persistence.accessModes | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} diff --git a/packages/text-embeddings/chart/values.yaml b/packages/text-embeddings/chart/values.yaml index 259f038ef..74da9c8b4 100644 --- a/packages/text-embeddings/chart/values.yaml +++ b/packages/text-embeddings/chart/values.yaml @@ -9,7 +9,7 @@ image: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. # x-release-please-start-version - tag: 0.9.2 + tag: 0.10.0 # x-release-please-end nameOverride: text-embeddings @@ -53,3 +53,8 @@ nodeSelector: {} tolerations: [] affinity: {} + +persistence: + size: 15Gi + accessModes: ReadWriteOnce + storageClass: "local-path" diff --git a/packages/text-embeddings/embedding-values.yaml b/packages/text-embeddings/embedding-values.yaml index 04a1455ff..864fc0a73 100644 --- a/packages/text-embeddings/embedding-values.yaml +++ b/packages/text-embeddings/embedding-values.yaml @@ -1,6 +1,14 @@ image: tag: "###ZARF_CONST_IMAGE_VERSION###" +gpu: + runtimeClassName: "###ZARF_VAR_GPU_CLASS_NAME###" + resources: limits: - nvidia.com/gpu: "###ZARF_VAR_GPU_LIMIT###" + nvidia.com/gpu: ###ZARF_VAR_GPU_LIMIT### + +persistence: + size: ###ZARF_VAR_PVC_SIZE### + accessModes: ###ZARF_VAR_PVC_ACCESS_MODE### + storageClass: ###ZARF_VAR_PVC_STORAGE_CLASS### diff --git a/packages/text-embeddings/main.py b/packages/text-embeddings/main.py index 6af2d6a72..0ad8ce824 100644 --- a/packages/text-embeddings/main.py +++ b/packages/text-embeddings/main.py @@ -1,5 +1,6 @@ import asyncio import logging +import os from InstructorEmbedding import INSTRUCTOR from leapfrogai_sdk import ( @@ -10,7 +11,8 @@ serve, ) -model = INSTRUCTOR("./.model") +model_dir = os.environ.get("LFAI_MODEL_PATH", ".model") +model = INSTRUCTOR(model_dir) class InstructorEmbedding: diff --git a/packages/text-embeddings/pyproject.toml b/packages/text-embeddings/pyproject.toml index 23666dcf4..574f2f1d5 100644 --- a/packages/text-embeddings/pyproject.toml +++ b/packages/text-embeddings/pyproject.toml @@ -3,12 +3,12 @@ name = "lfai-text-embeddings" description = "A LeapfrogAI API-compatible embeddings library wrapper for text-based embedding generation." # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ "InstructorEmbedding >= 1.0.1", - "torch == 2.1.2", + "torch == 2.2.0", "numpy == 1.26.3", "tqdm == 4.66.3", "sentence-transformers == 2.2.2", diff --git a/packages/text-embeddings/zarf.yaml b/packages/text-embeddings/zarf.yaml index d3bec755c..36fa59c6e 100644 --- a/packages/text-embeddings/zarf.yaml +++ b/packages/text-embeddings/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: @@ -16,6 +16,21 @@ variables: description: The GPU limit for the model inferencing. default: "0" pattern: "^[0-9]+$" + - name: GPU_CLASS_NAME + description: The GPU class name for the model inferencing. Leave blank for CPU-only. + default: "" + pattern: "^(nvidia)?$" + - name: PVC_SIZE + description: Size of the PVC used for model storage. + default: "15Gi" + pattern: "^[0-9]+[a-zA-Z]+$" + - name: PVC_ACCESS_MODE + description: Access mode of the PVC used for model storage. + default: "ReadWriteOnce" + pattern: "^(ReadWriteOnce|ReadOnlyMany|ReadWriteMany)$" + - name: PVC_STORAGE_CLASS + description: Storage class of the PVC used for model storage. + default: "local-path" components: - name: text-embeddings-model @@ -26,9 +41,26 @@ components: localPath: chart releaseName: text-embeddings-model # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "embedding-values.yaml" images: - ghcr.io/defenseunicorns/leapfrogai/text-embeddings:###ZARF_PKG_TMPL_IMAGE_VERSION### + - cgr.dev/chainguard/bash:latest + dataInjections: + - source: .model/ + target: + namespace: leapfrogai + selector: app=lfai-text-embeddings + container: data-loader + path: /data/.model + compress: true + actions: + onCreate: + before: + # NOTE: This assumes python is installed and in $PATH and 'huggingface_hub[cli,hf_transfer]' has been installed + - cmd: python scripts/model_download.py + env: + - REPO_ID=hkunlp/instructor-xl + - REVISION=ce48b213095e647a6c3536364b9fa00daf57f436 diff --git a/packages/ui/chart/Chart.yaml b/packages/ui/chart/Chart.yaml index 0ae233177..1bfbfbdfb 100644 --- a/packages/ui/chart/Chart.yaml +++ b/packages/ui/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/ui/chart/templates/ui/deployment.yaml b/packages/ui/chart/templates/ui/deployment.yaml index 5fca93f30..8015d2345 100644 --- a/packages/ui/chart/templates/ui/deployment.yaml +++ b/packages/ui/chart/templates/ui/deployment.yaml @@ -48,6 +48,18 @@ spec: value: '###ZARF_VAR_DISABLE_KEYCLOAK###' - name: PUBLIC_MESSAGE_LENGTH_LIMIT value: '###ZARF_VAR_MESSAGE_LENGTH_LIMIT###' + - name: SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL + value: "https://sso.{{ .Values.package.domain }}/realms/uds" + - name: SUPABASE_AUTH_KEYCLOAK_CLIENT_ID + valueFrom: + secretKeyRef: + name: sso-client-uds-supabase + key: clientId + - name: SUPABASE_AUTH_KEYCLOAK_SECRET + valueFrom: + secretKeyRef: + name: sso-client-uds-supabase + key: secret resources: requests: memory: '0' diff --git a/packages/ui/zarf.yaml b/packages/ui/zarf.yaml index 47ecc0281..83a233f1f 100644 --- a/packages/ui/zarf.yaml +++ b/packages/ui/zarf.yaml @@ -69,7 +69,7 @@ components: valuesFiles: - 'chart/ui-values.yaml' # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end images: - ghcr.io/defenseunicorns/leapfrogai/leapfrogai-ui:###ZARF_PKG_TMPL_IMAGE_VERSION### diff --git a/packages/vllm/Dockerfile b/packages/vllm/Dockerfile index b3d3028e8..859582c0e 100755 --- a/packages/vllm/Dockerfile +++ b/packages/vllm/Dockerfile @@ -10,7 +10,7 @@ ARG HF_HUB_ENABLE_HF_TRANSFER="1" ARG REPO_ID="TheBloke/Synthia-7B-v2.0-GPTQ" ARG REVISION="gptq-4bit-32g-actorder_True" ARG QUANTIZATION="gptq" -ARG MODEL_SOURCE=".model/" +ARG MODEL_SOURCE="/data/.model/" ARG MAX_CONTEXT_LENGTH=32768 ARG STOP_TOKENS='["","<|endoftext|>","<|im_end|>"]' ARG PROMPT_FORMAT_CHAT_SYSTEM="SYSTEM: {}\n" @@ -67,7 +67,7 @@ ARG HF_HUB_ENABLE_HF_TRANSFER="1" ARG REPO_ID="TheBloke/Synthia-7B-v2.0-GPTQ" ARG REVISION="gptq-4bit-32g-actorder_True" ARG QUANTIZATION="gptq" -ARG MODEL_SOURCE=".model/" +ARG MODEL_SOURCE="/data/.model/" ARG MAX_CONTEXT_LENGTH=32768 ARG STOP_TOKENS='["","<|endoftext|>","<|im_end|>"]' ARG PROMPT_FORMAT_CHAT_SYSTEM="SYSTEM: {}\n" @@ -121,9 +121,6 @@ ENV LAI_PROMPT_FORMAT_CHAT_USER=${PROMPT_FORMAT_CHAT_USER} ENV LAI_PROMPT_FORMAT_DEFAULTS_TOP_P=${PROMPT_FORMAT_DEFAULTS_TOP_P} ENV LAI_PROMPT_FORMAT_DEFAULTS_TOP_K=${PROMPT_FORMAT_DEFAULTS_TOP_K} -RUN pip install -U huggingface_hub[cli,hf_transfer] -RUN python3 packages/vllm/src/model_download.py - EXPOSE 50051:50051 ENTRYPOINT ["python", "-m", "leapfrogai_sdk.cli", "--app-dir=packages/vllm/src/", "main:Model"] diff --git a/packages/vllm/README.md b/packages/vllm/README.md index 59d705f48..eac93f3ef 100644 --- a/packages/vllm/README.md +++ b/packages/vllm/README.md @@ -38,6 +38,7 @@ To build and deploy just the VLLM Zarf package (from the root of the repository) > Deploy a [UDS cluster](/README.md#uds) if one isn't deployed already ```shell +pip install 'huggingface_hub[cli,hf_transfer]' # Used to download the model weights from huggingface make build-vllm LOCAL_VERSION=dev uds zarf package deploy packages/vllm/zarf-package-vllm-*-dev.tar.zst --confirm ``` diff --git a/packages/vllm/chart/Chart.yaml b/packages/vllm/chart/Chart.yaml index c89277682..0eddf27e6 100644 --- a/packages/vllm/chart/Chart.yaml +++ b/packages/vllm/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/vllm/chart/templates/deployment.yaml b/packages/vllm/chart/templates/deployment.yaml index 8d014f1c7..fcd927246 100644 --- a/packages/vllm/chart/templates/deployment.yaml +++ b/packages/vllm/chart/templates/deployment.yaml @@ -23,8 +23,39 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: + app: lfai-vllm {{- include "chart.selectorLabels" . | nindent 8 }} spec: + runtimeClassName: {{ .Values.gpu.runtimeClassName }} + # It's necessary to include the ###ZARF_DATA_INJECTION_MARKER### somewhere in the podspec, otherwise data injections will not occur. + initContainers: + - name: data-loader + image: cgr.dev/chainguard/bash:latest + securityContext: + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + # This command looks for the Zarf "data injection marker" which is a timestamped file that is injected after everything else and marks the injection as complete. + command: + [ + "sh", + "-c", + 'while [ ! -f /data/.model/###ZARF_DATA_INJECTION_MARKER### ]; do echo "waiting for zarf data sync" && sleep 1; done; echo "we are done waiting!"', + ] + resources: + requests: + memory: "64Mi" + cpu: "200m" + limits: + memory: "128Mi" + cpu: "500m" + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: /data + volumes: + - name: leapfrogai-pv-storage + persistentVolumeClaim: + claimName: lfai-vllm-pv-claim securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -39,6 +70,9 @@ spec: protocol: TCP resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: "/data" {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/packages/vllm/chart/templates/pvc.yaml b/packages/vllm/chart/templates/pvc.yaml new file mode 100644 index 000000000..81c617f70 --- /dev/null +++ b/packages/vllm/chart/templates/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: lfai-vllm-pv-claim + namespace: leapfrogai +spec: + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + accessModes: + - {{ .Values.persistence.accessModes | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} diff --git a/packages/vllm/chart/values.yaml b/packages/vllm/chart/values.yaml index 2e4582efc..d1a125885 100644 --- a/packages/vllm/chart/values.yaml +++ b/packages/vllm/chart/values.yaml @@ -9,7 +9,7 @@ image: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. # x-release-please-start-version - tag: 0.9.2 + tag: 0.10.0 # x-release-please-end nameOverride: vllm @@ -54,3 +54,8 @@ nodeSelector: {} tolerations: [] affinity: {} + +persistence: + size: 15Gi + accessModes: ReadWriteOnce + storageClass: "local-path" diff --git a/packages/vllm/pyproject.toml b/packages/vllm/pyproject.toml index b1b43efc4..f0b347a7a 100644 --- a/packages/vllm/pyproject.toml +++ b/packages/vllm/pyproject.toml @@ -3,7 +3,7 @@ name = "lfai-vllm" description = "A LeapfrogAI API-compatible VLLM wrapper for quantized and un-quantized model inferencing across GPU infrastructures." # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ diff --git a/packages/vllm/vllm-values.yaml b/packages/vllm/vllm-values.yaml index 3e3c22379..90631193e 100644 --- a/packages/vllm/vllm-values.yaml +++ b/packages/vllm/vllm-values.yaml @@ -1,2 +1,10 @@ image: tag: "###ZARF_CONST_IMAGE_VERSION###" + +gpu: + runtimeClassName: nvidia + +persistence: + size: ###ZARF_VAR_PVC_SIZE### + accessModes: ###ZARF_VAR_PVC_ACCESS_MODE### + storageClass: ###ZARF_VAR_PVC_STORAGE_CLASS### diff --git a/packages/vllm/zarf.yaml b/packages/vllm/zarf.yaml index 1de33ae5b..f69227aa9 100644 --- a/packages/vllm/zarf.yaml +++ b/packages/vllm/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: name: vllm @@ -10,6 +10,19 @@ constants: - name: IMAGE_VERSION value: "###ZARF_PKG_TMPL_IMAGE_VERSION###" +variables: + - name: PVC_SIZE + description: Size of the PVC used for model storage. + default: "15Gi" + pattern: "^[0-9]+[a-zA-Z]+$" + - name: PVC_ACCESS_MODE + description: Access mode of the PVC used for model storage. + default: "ReadWriteOnce" + pattern: "^(ReadWriteOnce|ReadOnlyMany|ReadWriteMany)$" + - name: PVC_STORAGE_CLASS + description: Storage class of the PVC used for model storage. + default: "local-path" + components: - name: vllm-model required: true @@ -19,9 +32,28 @@ components: localPath: chart releaseName: vllm-model # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "vllm-values.yaml" images: - ghcr.io/defenseunicorns/leapfrogai/vllm:###ZARF_PKG_TMPL_IMAGE_VERSION### + - cgr.dev/chainguard/bash:latest + dataInjections: + - source: .model/ + target: + namespace: leapfrogai + selector: app=lfai-vllm + container: data-loader + path: /data/.model + compress: true + actions: + onCreate: + before: + # NOTE: This assumes python is installed and in $PATH and 'huggingface_hub[cli,hf_transfer]' has been installed + - cmd: python src/model_download.py + env: + - LAI_REPO_ID=TheBloke/Synthia-7B-v2.0-GPTQ + - LAI_REVISION=gptq-4bit-32g-actorder_True + - LAI_QUANTIZATION=gptq + - LAI_HF_HUB_ENABLE_HF_TRANSFER=1 diff --git a/packages/whisper/Dockerfile b/packages/whisper/Dockerfile index 1686c8d3c..e7568da48 100644 --- a/packages/whisper/Dockerfile +++ b/packages/whisper/Dockerfile @@ -17,16 +17,10 @@ COPY packages/whisper packages/whisper RUN rm -f packages/whisper/build/*.whl RUN python -m pip wheel "packages/whisper[dev]" -w packages/whisper/build --find-links=${SDK_DEST} -# download and covnert OpenAI's whisper base -ARG MODEL_NAME=openai/whisper-base -RUN pip install ctranslate2 transformers[torch] --no-index --find-links=packages/whisper/build/ -RUN ct2-transformers-converter --model ${MODEL_NAME} --output_dir .model --copy_files tokenizer.json --quantization float32 -RUN pip uninstall -y ctranslate2 transformers[torch] - RUN pip install packages/whisper/build/lfai_whisper*.whl --no-index --find-links=packages/whisper/build/ # Use hardened ffmpeg image to get compiled binaries -FROM cgr.dev/chainguard/ffmpeg:latest as ffmpeg +FROM cgr.dev/chainguard/ffmpeg:latest AS ffmpeg # hardened and slim python image FROM ghcr.io/defenseunicorns/leapfrogai/python:3.11 @@ -40,7 +34,6 @@ COPY --from=ffmpeg /usr/bin/ffprobe /usr/bin COPY --from=ffmpeg /usr/lib/lib* /usr/lib COPY --from=builder /leapfrogai/.venv/ /leapfrogai/.venv/ -COPY --from=builder /leapfrogai/.model/ /leapfrogai/.model/ # set the path to the cuda 11.8 dependencies ENV LD_LIBRARY_PATH \ diff --git a/packages/whisper/README.md b/packages/whisper/README.md index 327d5a810..ddf0b1008 100644 --- a/packages/whisper/README.md +++ b/packages/whisper/README.md @@ -12,6 +12,8 @@ To build and deploy just the whisper Zarf package (from the root of the reposito > Deploy a [UDS cluster](/README.md#uds) if one isn't deployed already ```shell +pip install 'ctranslate2' # Used to download and convert the model weights +pip install 'transformers[torch]' # Used to download and convert the model weights make build-whisper LOCAL_VERSION=dev uds zarf package deploy packages/whisper/zarf-package-whisper-*-dev.tar.zst --confirm ``` diff --git a/packages/whisper/chart/Chart.yaml b/packages/whisper/chart/Chart.yaml index c89277682..0eddf27e6 100644 --- a/packages/whisper/chart/Chart.yaml +++ b/packages/whisper/chart/Chart.yaml @@ -16,7 +16,7 @@ type: application # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) # x-release-please-start-version -version: 0.9.2 +version: 0.10.0 # x-release-please-end # This is the version number of the application being deployed. This version number should be diff --git a/packages/whisper/chart/templates/deployment.yaml b/packages/whisper/chart/templates/deployment.yaml index da60b2778..40139d946 100644 --- a/packages/whisper/chart/templates/deployment.yaml +++ b/packages/whisper/chart/templates/deployment.yaml @@ -23,8 +23,43 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} labels: + app: lfai-whisper {{- include "chart.selectorLabels" . | nindent 8 }} spec: + {{- if gt (index .Values.resources.limits "nvidia.com/gpu") 0.0 }} + runtimeClassName: nvidia + {{- else if .Values.gpu.runtimeClassName }} + runtimeClassName: {{ .Values.gpu.runtimeClassName }} + {{- end }} + # It's necessary to include the ###ZARF_DATA_INJECTION_MARKER### somewhere in the podspec, otherwise data injections will not occur. + initContainers: + - name: data-loader + image: cgr.dev/chainguard/bash:latest + securityContext: + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + # This command looks for the Zarf "data injection marker" which is a timestamped file that is injected after everything else and marks the injection as complete. + command: + [ + "sh", + "-c", + 'while [ ! -f /data/.model/###ZARF_DATA_INJECTION_MARKER### ]; do echo "waiting for zarf data sync" && sleep 1; done; echo "we are done waiting!"', + ] + resources: + requests: + memory: "64Mi" + cpu: "200m" + limits: + memory: "128Mi" + cpu: "500m" + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: /data + volumes: + - name: leapfrogai-pv-storage + persistentVolumeClaim: + claimName: lfai-whisper-pv-claim securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: @@ -38,10 +73,15 @@ spec: containerPort: {{ .Values.service.port }} protocol: TCP env: + - name: LFAI_MODEL_PATH + value: "/data/.model" - name: GPU_REQUEST - value: "{{ (index .Values.resources.requests "nvidia.com/gpu") | default "0" }}" + value: "{{ (index .Values.resources.limits "nvidia.com/gpu") | default "0" }}" resources: {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: leapfrogai-pv-storage + mountPath: "/data" {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/packages/whisper/chart/templates/pvc.yaml b/packages/whisper/chart/templates/pvc.yaml new file mode 100644 index 000000000..02e81eec0 --- /dev/null +++ b/packages/whisper/chart/templates/pvc.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: lfai-whisper-pv-claim + namespace: leapfrogai +spec: + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + accessModes: + - {{ .Values.persistence.accessModes | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} diff --git a/packages/whisper/chart/values.yaml b/packages/whisper/chart/values.yaml index f55de8e65..c08319215 100644 --- a/packages/whisper/chart/values.yaml +++ b/packages/whisper/chart/values.yaml @@ -9,7 +9,7 @@ image: pullPolicy: Always # Overrides the image tag whose default is the chart appVersion. # x-release-please-start-version - tag: 0.9.2 + tag: 0.10.0 # x-release-please-end nameOverride: whisper @@ -53,3 +53,8 @@ nodeSelector: {} tolerations: [] affinity: {} + +persistence: + size: 15Gi + accessModes: ReadWriteOnce + storageClass: "local-path" diff --git a/packages/whisper/main.py b/packages/whisper/main.py index 517b21304..467a7039a 100644 --- a/packages/whisper/main.py +++ b/packages/whisper/main.py @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) -model_path = ".model" +model_path = os.environ.get("LFAI_MODEL_PATH", ".model") GPU_ENABLED = True if int(os.environ.get("GPU_REQUEST", 0)) > 0 else False @@ -18,10 +18,32 @@ def make_transcribe_request(filename, task, language, temperature, prompt): device = "cuda" if GPU_ENABLED else "cpu" model = WhisperModel(model_path, device=device, compute_type="float32") - segments, info = model.transcribe(filename, task=task, beam_size=5) + # Prepare kwargs with non-None values + kwargs = {} + if task: + if task in ["transcribe", "translate"]: + kwargs["task"] = task + else: + logger.error(f"Task {task} is not supported") + return {"text": ""} + if language: + if language in model.supported_languages: + kwargs["language"] = language + else: + logger.error(f"Language {language} is not supported") + if temperature: + kwargs["temperature"] = temperature + if prompt: + kwargs["initial_prompt"] = prompt + + try: + # Call transcribe with only non-None parameters + segments, info = model.transcribe(filename, beam_size=5, **kwargs) + except Exception as e: + logger.error(f"Error transcribing audio: {e}") + return {"text": ""} output = "" - for segment in segments: output += segment.text diff --git a/packages/whisper/pyproject.toml b/packages/whisper/pyproject.toml index 9c8bd81a1..79123cba5 100644 --- a/packages/whisper/pyproject.toml +++ b/packages/whisper/pyproject.toml @@ -3,12 +3,13 @@ name = "lfai-whisper" description = "A LeapfrogAI API-compatible faster-whisper wrapper for audio transcription generation across CPU and GPU infrastructures." # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ - "faster-whisper == 0.10.0", + "faster-whisper == 1.0.3", "leapfrogai-sdk", + "openai-whisper == 20231117", ] requires-python = "~=3.11" readme = "README.md" diff --git a/packages/whisper/whisper-values.yaml b/packages/whisper/whisper-values.yaml index 04a1455ff..864fc0a73 100644 --- a/packages/whisper/whisper-values.yaml +++ b/packages/whisper/whisper-values.yaml @@ -1,6 +1,14 @@ image: tag: "###ZARF_CONST_IMAGE_VERSION###" +gpu: + runtimeClassName: "###ZARF_VAR_GPU_CLASS_NAME###" + resources: limits: - nvidia.com/gpu: "###ZARF_VAR_GPU_LIMIT###" + nvidia.com/gpu: ###ZARF_VAR_GPU_LIMIT### + +persistence: + size: ###ZARF_VAR_PVC_SIZE### + accessModes: ###ZARF_VAR_PVC_ACCESS_MODE### + storageClass: ###ZARF_VAR_PVC_STORAGE_CLASS### diff --git a/packages/whisper/zarf.yaml b/packages/whisper/zarf.yaml index 52a44efc6..a10252027 100644 --- a/packages/whisper/zarf.yaml +++ b/packages/whisper/zarf.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json kind: ZarfPackageConfig metadata: @@ -16,6 +16,22 @@ variables: description: The GPU limit for the model inferencing. default: "0" pattern: "^[0-9]+$" + - name: GPU_CLASS_NAME + description: The GPU class name for the model inferencing. Leave blank for CPU-only. + default: "" + pattern: "^(nvidia)?$" + - name: PVC_SIZE + description: Size of the PVC used for model storage. + default: "15Gi" + pattern: "^[0-9]+[a-zA-Z]+$" + - name: PVC_ACCESS_MODE + description: Access mode of the PVC used for model storage. + default: "ReadWriteOnce" + pattern: "^(ReadWriteOnce|ReadOnlyMany|ReadWriteMany)$" + - name: PVC_STORAGE_CLASS + description: Storage class of the PVC used for model storage. + default: "local-path" + components: - name: whisper-model @@ -26,9 +42,30 @@ components: localPath: chart releaseName: whisper-model # x-release-please-start-version - version: 0.9.2 + version: 0.10.0 # x-release-please-end valuesFiles: - "whisper-values.yaml" images: - ghcr.io/defenseunicorns/leapfrogai/whisper:###ZARF_PKG_TMPL_IMAGE_VERSION### + - cgr.dev/chainguard/bash:latest + dataInjections: + - source: .model/ + target: + namespace: leapfrogai + selector: app=lfai-whisper + container: data-loader + path: /data/.model + compress: true + actions: + onCreate: + before: + # NOTE: This assumes python is installed and in $PATH and 'ctranslate2' and 'transformers[torch]' has been installed + - cmd: | + ct2-transformers-converter --model ${MODEL_NAME} \ + --output_dir .model \ + --copy_files tokenizer.json special_tokens_map.json preprocessor_config.json normalizer.json tokenizer_config.json vocab.json \ + --quantization float32 \ + --force + env: + - MODEL_NAME=openai/whisper-base diff --git a/pyproject.toml b/pyproject.toml index f86babfda..e794fec99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ description = "ChatGPT type capabilities for secure, local, air-gapped systems." authors = [{ name = "LeapfrogAI Team", email = "ai@defenseunicorns.com" }] # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end readme = "README.md" @@ -13,15 +13,31 @@ license = {file = "LICENSE"} dependencies = [ # Dev dependencies needed for all of lfai "openai", "pip-tools == 7.3.0", - "pytest", - "pytest-asyncio", "httpx", "ruff", - "python-dotenv", - "pytest-asyncio" + "python-dotenv" ] requires-python = "~=3.11" +[project.optional-dependencies] +dev = [ + "locust", + "pytest-asyncio", + "requests", + "requests-toolbelt", + "pytest", + "huggingface_hub[cli,hf_transfer]" +] + +dev-whisper = [ + "ctranslate2", + "transformers[torch]" +] + +dev-vllm = [ + "confz" +] + [tool.pip-tools] generate-hashes = true diff --git a/src/leapfrogai_api/Makefile b/src/leapfrogai_api/Makefile index 470f885db..adc2a2b20 100644 --- a/src/leapfrogai_api/Makefile +++ b/src/leapfrogai_api/Makefile @@ -1,14 +1,19 @@ +MAKEFILE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) SHELL := /bin/bash export SUPABASE_URL=$(shell supabase status | grep -oP '(?<=API URL: ).*') export SUPABASE_ANON_KEY=$(shell supabase status | grep -oP '(?<=anon key: ).*') -install: +install-api: + @cd ${MAKEFILE_DIR} && \ python -m pip install ../../src/leapfrogai_sdk + @cd ${MAKEFILE_DIR} && \ python -m pip install -e . + python -m pip install "../../.[dev]" -dev: +dev-run-api: + @cd ${MAKEFILE_DIR} && \ python -m uvicorn main:app --port 3000 --reload --log-level info define get_jwt_token @@ -36,4 +41,4 @@ env: $(call get_jwt_token,"${SUPABASE_URL}/auth/v1/token?grant_type=password") test-integration: - cd ../../ && python -m pytest tests/integration/api/ -vv -s + @cd ${MAKEFILE_DIR} && python -m pytest ../../tests/integration/api/ -vv -s diff --git a/src/leapfrogai_api/backend/rag/index.py b/src/leapfrogai_api/backend/rag/index.py index 97da39a07..ce4ade400 100644 --- a/src/leapfrogai_api/backend/rag/index.py +++ b/src/leapfrogai_api/backend/rag/index.py @@ -3,8 +3,6 @@ import logging import tempfile import time - - from fastapi import HTTPException, UploadFile, status from langchain_core.documents import Document from langchain_core.embeddings import Embeddings @@ -28,6 +26,8 @@ FilterVectorStoreFile, ) +from leapfrogai_api.data.crud_vector_content import CRUDVectorContent, Vector + # Allows for overwriting type of embeddings that will be instantiated embeddings_type: type[Embeddings] | type[LeapfrogAIEmbeddings] | None = ( LeapfrogAIEmbeddings @@ -56,14 +56,12 @@ async def index_file(self, vector_store_id: str, file_id: str) -> VectorStoreFil if await crud_vector_store_file.get( filters=FilterVectorStoreFile(vector_store_id=vector_store_id, id=file_id) ): - print("File already indexed: %s", file_id) logging.error("File already indexed: %s", file_id) raise FileAlreadyIndexedError("File already indexed") if not ( await crud_vector_store.get(filters=FilterVectorStore(id=vector_store_id)) ): - print("Vector store doesn't exist: %s", vector_store_id) logging.error("Vector store doesn't exist: %s", vector_store_id) raise ValueError("Vector store not found") @@ -175,7 +173,7 @@ async def create_new_vector_store( ), last_active_at=last_active_at, # Set to current time metadata=request.metadata, - name=request.name, + name=request.name or "", object="vector_store", status=VectorStoreStatus.IN_PROGRESS.value, expires_after=expires_after, @@ -200,7 +198,6 @@ async def create_new_vector_store( object_=new_vector_store, ) except Exception as exc: - logging.error(exc) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Unable to parse vector store request", @@ -291,29 +288,6 @@ async def file_ids_are_valid(self, file_ids: str | list[str]) -> bool: return True - async def adelete_file(self, vector_store_id: str, file_id: str) -> bool: - """Delete a file from the vector store. - - Args: - vector_store_id (str): The ID of the vector store. - file_id (str): The ID of the file to be deleted. - - Returns: - dict: The response from the database after deleting the file. - - """ - data, _count = ( - await self.db.from_(self.table_name) - .delete() - .eq("vector_store_id", vector_store_id) - .eq("file_id", file_id) - .execute() - ) - - _, response = data - - return bool(response) - async def aadd_documents( self, documents: list[Document], @@ -326,7 +300,8 @@ async def aadd_documents( documents (list[Document]): A list of Langchain Document objects to be added. vector_store_id (str): The ID of the vector store where the documents will be added. file_id (str): The ID of the file associated with the documents. - batch_size (int): The size of the batches that will be pushed to the db. This value defaults to 100 + batch_size (int): The size of the batches that will + be pushed to the db. This value defaults to 100 as a balance between the memory impact of large files and performance improvements from batching. Returns: List[str]: A list of IDs assigned to the added documents. @@ -338,22 +313,25 @@ async def aadd_documents( texts=[document.page_content for document in documents] ) - vectors = [] + vectors: list[Vector] = [] for document, embedding in zip(documents, embeddings): - vector = { - "content": document.page_content, - "metadata": document.metadata, - "embedding": embedding, - } + vector = Vector( + id="", + vector_store_id=vector_store_id, + file_id=file_id, + content=document.page_content, + metadata=document.metadata, + embedding=embedding, + ) vectors.append(vector) + crud_vector_content = CRUDVectorContent(db=self.db) + for i in range(0, len(vectors), batch_size): batch = vectors[i : i + batch_size] - response = await self._aadd_vectors( - vector_store_id=vector_store_id, file_id=file_id, vectors=batch - ) - ids.extend([item["id"] for item in response]) + response = await crud_vector_content.add_vectors(batch) + ids.extend([item.id for item in response]) return ids async def asimilarity_search(self, query: str, vector_store_id: str, k: int = 4): @@ -370,20 +348,10 @@ async def asimilarity_search(self, query: str, vector_store_id: str, k: int = 4) """ vector = await self.embeddings.aembed_query(query) - user_id: str = (await self.db.auth.get_user()).user.id - - params = { - "query_embedding": vector, - "match_limit": k, - "vs_id": vector_store_id, - "user_id": user_id, - } - - query_builder = self.db.rpc(self.query_name, params=params) - - response = await query_builder.execute() - - return response + crud_vector_content = CRUDVectorContent(db=self.db) + return await crud_vector_content.similarity_search( + query=vector, vector_store_id=vector_store_id, k=k + ) async def _increment_vector_store_file_status( self, vector_store: VectorStore, file_response: VectorStoreFile @@ -398,64 +366,3 @@ async def _increment_vector_store_file_status( elif file_response.status == VectorStoreFileStatus.CANCELLED.value: vector_store.file_counts.cancelled += 1 vector_store.file_counts.total += 1 - - async def _adelete_vector( - self, - vector_store_id: str, - file_id: str, - ) -> dict: - """Delete a vector from the vector store. - - Args: - vector_store_id (str): The ID of the vector store. - file_id (str): The ID of the file associated with the vector. - - Returns: - dict: The response from the database after deleting the vector. - - """ - response = ( - await self.db.from_(self.table_name) - .delete() - .eq("vector_store_id", vector_store_id) - .eq("file_id", file_id) - .execute() - ) - return response - - async def _aadd_vectors( - self, vector_store_id: str, file_id: str, vectors: list[dict[str, any]] - ) -> dict: - """Add multiple vectors to the vector store in a batch. - - Args: - vector_store_id (str): The ID of the vector store. - file_id (str): The ID of the file associated with the vectors. - vectors (list[dict]): A list of dictionaries containing vector data. - - Returns: - dict: The response from the database after inserting the vectors. - """ - user_id: str = (await self.db.auth.get_user()).user.id - - rows = [] - for vector in vectors: - row = { - "user_id": user_id, - "vector_store_id": vector_store_id, - "file_id": file_id, - "content": vector["content"], - "metadata": vector["metadata"], - "embedding": vector["embedding"], - } - rows.append(row) - - data, _count = await self.db.from_(self.table_name).insert(rows).execute() - - _, response = data - - for item in response: - if "user_id" in item: - del item["user_id"] - - return response diff --git a/src/leapfrogai_api/backend/rag/leapfrogai_embeddings.py b/src/leapfrogai_api/backend/rag/leapfrogai_embeddings.py index 03b9a3dde..0731a8e74 100644 --- a/src/leapfrogai_api/backend/rag/leapfrogai_embeddings.py +++ b/src/leapfrogai_api/backend/rag/leapfrogai_embeddings.py @@ -4,6 +4,7 @@ import leapfrogai_sdk as lfai from leapfrogai_api.utils import get_model_config from leapfrogai_api.backend.grpc_client import create_embeddings +import logging # Partially implements the Langchain Core Embeddings interface @@ -44,7 +45,9 @@ async def aembed_query(self, text: str) -> list[float]: return list_of_embeddings[0] - async def _get_model(self, model_name: str = os.getenv("DEFAULT_EMBEDDINGS_MODEL")): + async def _get_model( + self, model_name: str = os.getenv("DEFAULT_EMBEDDINGS_MODEL", "text-embeddings") + ): """Gets the embeddings model. Args: @@ -58,6 +61,7 @@ async def _get_model(self, model_name: str = os.getenv("DEFAULT_EMBEDDINGS_MODEL """ if not (model := get_model_config().get_model_backend(model=model_name)): + logging.error(f"Embeddings model {model_name} not found.") raise ValueError("Embeddings model not found.") return model diff --git a/src/leapfrogai_api/backend/types.py b/src/leapfrogai_api/backend/types.py index 8024f9da1..59011003c 100644 --- a/src/leapfrogai_api/backend/types.py +++ b/src/leapfrogai_api/backend/types.py @@ -682,31 +682,31 @@ class ModifyMessageRequest(BaseModel): ################ -# LEAPFROGAI RAG +# LEAPFROGAI Vector Stores ################ -class RAGItem(BaseModel): - """Object representing a single item in a Retrieval-Augmented Generation (RAG) result.""" +class SearchItem(BaseModel): + """Object representing a single item in a search result.""" - id: str = Field(..., description="Unique identifier for the RAG item.") + id: str = Field(..., description="Unique identifier for the search item.") vector_store_id: str = Field( ..., description="ID of the vector store containing this item." ) file_id: str = Field(..., description="ID of the file associated with this item.") - content: str = Field(..., description="The actual content of the RAG item.") + content: str = Field(..., description="The actual content of the item.") metadata: dict = Field( - ..., description="Additional metadata associated with the RAG item." + ..., description="Additional metadata associated with the item." ) similarity: float = Field( ..., description="Similarity score of this item to the query." ) -class RAGResponse(BaseModel): +class SearchResponse(BaseModel): """Response object for RAG queries.""" - data: list[RAGItem] = Field( + data: list[SearchItem] = Field( ..., description="List of RAG items returned as a result of the query.", min_length=0, diff --git a/src/leapfrogai_api/data/crud_base.py b/src/leapfrogai_api/data/crud_base.py index 7f6b5db6d..8532df611 100644 --- a/src/leapfrogai_api/data/crud_base.py +++ b/src/leapfrogai_api/data/crud_base.py @@ -118,10 +118,16 @@ async def delete(self, filters: dict | None = None) -> bool: async def _get_user_id(self) -> str: """Get the user_id from the API key.""" - if self.db.options.headers.get("x-custom-api-key"): - result = await self.db.table("api_keys").select("user_id").execute() - user_id: str = result.data[0]["user_id"] - else: - user_id = (await self.db.auth.get_user()).user.id + return await get_user_id(self.db) - return user_id + +async def get_user_id(db: AsyncClient) -> str: + """Get the user_id from the API key.""" + + if db.options.headers.get("x-custom-api-key"): + result = await db.table("api_keys").select("user_id").execute() + user_id: str = result.data[0]["user_id"] + else: + user_id = (await db.auth.get_user()).user.id + + return user_id diff --git a/src/leapfrogai_api/data/crud_vector_content.py b/src/leapfrogai_api/data/crud_vector_content.py new file mode 100644 index 000000000..5cf5c34f2 --- /dev/null +++ b/src/leapfrogai_api/data/crud_vector_content.py @@ -0,0 +1,102 @@ +"""CRUD Operations for VectorStore.""" + +from pydantic import BaseModel +from supabase import AClient as AsyncClient +from leapfrogai_api.data.crud_base import get_user_id +import ast + + +class Vector(BaseModel): + id: str = "" + vector_store_id: str + file_id: str + content: str + metadata: dict + embedding: list[float] + + +class CRUDVectorContent: + """CRUD Operations for VectorStore""" + + def __init__(self, db: AsyncClient): + self.db = db + self.table_name = "vector_content" + + async def add_vectors(self, object_: list[Vector]) -> list[Vector]: + """Create new row.""" + + user_id = await get_user_id(self.db) + + rows = [] + + for vector in object_: + dict_ = vector.model_dump() + dict_["user_id"] = user_id + if "id" in dict_: + del dict_["id"] + + rows.append(dict_) + + data, _count = await self.db.table(self.table_name).insert(dict_).execute() + + _, response = data + + final_response = [] + try: + for item in response: + if "user_id" in item: + del item["user_id"] + if isinstance(item["embedding"], str): + item["embedding"] = self.string_to_float_list(item["embedding"]) + final_response.append( + Vector( + id=item["id"], + vector_store_id=item["vector_store_id"], + file_id=item["file_id"], + content=item["content"], + metadata=item["metadata"], + embedding=item["embedding"], + ) + ) + + return final_response + except Exception as e: + raise e + + async def delete_vectors(self, vector_store_id: str, file_id: str) -> bool: + """Delete a vector store file by its ID.""" + data, _count = ( + await self.db.table(self.table_name) + .delete() + .eq("vector_store_id", vector_store_id) + .eq("file_id", file_id) + .execute() + ) + + _, response = data + + return bool(response) + + async def similarity_search(self, query: list[float], vector_store_id: str, k: int): + user_id = await get_user_id(self.db) + + params = { + "query_embedding": query, + "match_limit": k, + "vs_id": vector_store_id, + "user_id": user_id, + } + + return await self.db.rpc("match_vectors", params).execute() + + @staticmethod + def string_to_float_list(s: str) -> list[float]: + try: + # Remove any whitespace and convert to a Python list + cleaned_string = s.strip() + python_list = ast.literal_eval(cleaned_string) + + # Convert all elements to float + return [float(x) for x in python_list] + except (ValueError, SyntaxError) as e: + raise e diff --git a/src/leapfrogai_api/main.py b/src/leapfrogai_api/main.py index d3207169b..fa0a1e056 100644 --- a/src/leapfrogai_api/main.py +++ b/src/leapfrogai_api/main.py @@ -3,32 +3,30 @@ import asyncio import logging from contextlib import asynccontextmanager + from fastapi import FastAPI +from fastapi.exception_handlers import request_validation_exception_handler +from fastapi.exceptions import RequestValidationError from leapfrogai_api.routers.base import router as base_router -from leapfrogai_api.routers.leapfrogai import ( - auth, - rag, -) +from leapfrogai_api.routers.leapfrogai import auth +from leapfrogai_api.routers.leapfrogai import models as lfai_models +from leapfrogai_api.routers.leapfrogai import vector_stores as lfai_vector_stores from leapfrogai_api.routers.openai import ( + assistants, audio, - completions, chat, + completions, embeddings, - models, - assistants, files, - threads, messages, + models, runs, runs_steps, + threads, vector_stores, ) from leapfrogai_api.utils import get_model_config -from fastapi.exception_handlers import ( - request_validation_exception_handler, -) -from fastapi.exceptions import RequestValidationError # handle startup & shutdown tasks @@ -71,7 +69,8 @@ async def validation_exception_handler(request, exc): app.include_router(runs.router) app.include_router(messages.router) app.include_router(runs_steps.router) -app.include_router(rag.router) +app.include_router(lfai_vector_stores.router) +app.include_router(lfai_models.router) # This should be at the bottom to prevent it preempting more specific runs endpoints # https://fastapi.tiangolo.com/tutorial/path-params/#order-matters app.include_router(threads.router) diff --git a/src/leapfrogai_api/pyproject.toml b/src/leapfrogai_api/pyproject.toml index 96c61cfb3..1ffe367c6 100644 --- a/src/leapfrogai_api/pyproject.toml +++ b/src/leapfrogai_api/pyproject.toml @@ -3,7 +3,7 @@ name = "leapfrogai-api" description = "An API for LeapfrogAI that allows LeapfrogAI backends to connect seamlessly" # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ diff --git a/src/leapfrogai_api/routers/base.py b/src/leapfrogai_api/routers/base.py index 150a53f90..58032a62c 100644 --- a/src/leapfrogai_api/routers/base.py +++ b/src/leapfrogai_api/routers/base.py @@ -1,7 +1,7 @@ """Base router for the API.""" from fastapi import APIRouter -from leapfrogai_api.utils import get_model_config + router = APIRouter(tags=["/"]) @@ -10,9 +10,3 @@ async def healthz(): """Health check endpoint.""" return {"status": "ok"} - - -@router.get("/models") -async def models(): - """List all the models.""" - return get_model_config() diff --git a/src/leapfrogai_api/routers/leapfrogai/auth.py b/src/leapfrogai_api/routers/leapfrogai/auth.py index 9ef989d75..897f23a8b 100644 --- a/src/leapfrogai_api/routers/leapfrogai/auth.py +++ b/src/leapfrogai_api/routers/leapfrogai/auth.py @@ -42,7 +42,7 @@ class ModifyAPIKeyRequest(BaseModel): ) -@router.post("/create-api-key") +@router.post("/api-keys") async def create_api_key( session: Session, request: CreateAPIKeyRequest, @@ -50,6 +50,8 @@ async def create_api_key( """ Create an API key. + Accessible only with a valid JWT, not an API key. + WARNING: The API key is only returned once. Store it securely. """ @@ -71,24 +73,32 @@ async def create_api_key( return await crud_api_key.create(new_api_key) -@router.get("/list-api-keys") +@router.get("/api-keys") async def list_api_keys( session: Session, ) -> list[APIKeyItem]: - """List all API keys.""" + """ + List all API keys. + + Accessible only with a valid JWT, not an API key. + """ crud_api_key = CRUDAPIKey(session) return await crud_api_key.list() -@router.post("/update-api-key/{api_key_id}") +@router.patch("/api-keys/{api_key_id}") async def update_api_key( session: Session, api_key_id: Annotated[str, Field(description="The UUID of the API key.")], request: ModifyAPIKeyRequest, ) -> APIKeyItem: - """Update an API key.""" + """ + Update an API key. + + Accessible only with a valid JWT, not an API key. + """ crud_api_key = CRUDAPIKey(session) @@ -100,6 +110,15 @@ async def update_api_key( detail="API key not found.", ) + if request.expires_at and ( + request.expires_at > api_key.expires_at + or request.expires_at <= int(time.time()) + ): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Invalid expiration time. New expiration must be in the future but less than the current expiration.", + ) + updated_api_key = APIKeyItem( name=request.name if request.name else api_key.name, id=api_key_id, @@ -113,12 +132,16 @@ async def update_api_key( return await crud_api_key.update(api_key_id, updated_api_key) -@router.delete("/revoke-api-key/{api_key_id}", status_code=status.HTTP_204_NO_CONTENT) +@router.delete("/api-keys/{api_key_id}", status_code=status.HTTP_204_NO_CONTENT) async def revoke_api_key( session: Session, api_key_id: Annotated[str, Field(description="The UUID of the API key.")], ): - """Revoke an API key.""" + """ + Revoke an API key. + + Accessible only with a valid JWT, not an API key. + """ crud_api_key = CRUDAPIKey(session) diff --git a/src/leapfrogai_api/routers/leapfrogai/models.py b/src/leapfrogai_api/routers/leapfrogai/models.py new file mode 100644 index 000000000..27b750a2a --- /dev/null +++ b/src/leapfrogai_api/routers/leapfrogai/models.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter +from leapfrogai_api.utils import get_model_config + +router = APIRouter(prefix="/leapfrogai/v1/models", tags=["leapfrogai/models"]) + + +@router.get("") +async def models(): + """List all the models.""" + return get_model_config() diff --git a/src/leapfrogai_api/routers/leapfrogai/rag.py b/src/leapfrogai_api/routers/leapfrogai/vector_stores.py similarity index 61% rename from src/leapfrogai_api/routers/leapfrogai/rag.py rename to src/leapfrogai_api/routers/leapfrogai/vector_stores.py index 99da0d558..8d3db3ec3 100644 --- a/src/leapfrogai_api/routers/leapfrogai/rag.py +++ b/src/leapfrogai_api/routers/leapfrogai/vector_stores.py @@ -3,21 +3,23 @@ from fastapi import APIRouter from postgrest.base_request_builder import SingleAPIResponse from leapfrogai_api.backend.rag.query import QueryService -from leapfrogai_api.backend.types import RAGResponse +from leapfrogai_api.backend.types import SearchResponse from leapfrogai_api.routers.supabase_session import Session -router = APIRouter(prefix="/leapfrogai/v1/rag", tags=["leapfrogai/rag"]) +router = APIRouter( + prefix="/leapfrogai/v1/vector_stores", tags=["leapfrogai/vector_stores"] +) -@router.post("") -async def query_rag( +@router.post("/search") +async def search( session: Session, query: str, vector_store_id: str, k: int = 5, -) -> RAGResponse: +) -> SearchResponse: """ - Query the RAG (Retrieval-Augmented Generation). + Performs a similarity search of the vector store. Args: session (Session): The database session. @@ -26,13 +28,13 @@ async def query_rag( k (int, optional): The number of results to retrieve. Defaults to 5. Returns: - RAGResponse: The response from the RAG. + SearchResponse: The search response from the vector store. """ query_service = QueryService(db=session) - result: SingleAPIResponse[RAGResponse] = await query_service.query_rag( + result: SingleAPIResponse[SearchResponse] = await query_service.query_rag( query=query, vector_store_id=vector_store_id, k=k, ) - return RAGResponse(data=result.data) + return SearchResponse(data=result.data) diff --git a/src/leapfrogai_api/routers/openai/requests/run_create_params_request_base.py b/src/leapfrogai_api/routers/openai/requests/run_create_params_request_base.py index eeefba78f..3f9834340 100644 --- a/src/leapfrogai_api/routers/openai/requests/run_create_params_request_base.py +++ b/src/leapfrogai_api/routers/openai/requests/run_create_params_request_base.py @@ -50,7 +50,7 @@ from leapfrogai_api.backend.rag.query import QueryService from leapfrogai_api.backend.types import ( ChatMessage, - RAGResponse, + SearchResponse, ChatCompletionResponse, ChatCompletionRequest, ChatChoice, @@ -263,12 +263,14 @@ def sort_by_created_at(msg: Message): for vector_store_id in vector_store_ids: rag_results_raw: SingleAPIResponse[ - RAGResponse + SearchResponse ] = await query_service.query_rag( query=first_message.content, vector_store_id=vector_store_id, ) - rag_responses: RAGResponse = RAGResponse(data=rag_results_raw.data) + rag_responses: SearchResponse = SearchResponse( + data=rag_results_raw.data + ) # Insert the RAG response messages just before the user's query for count, rag_response in enumerate(rag_responses.data): diff --git a/src/leapfrogai_api/routers/openai/vector_stores.py b/src/leapfrogai_api/routers/openai/vector_stores.py index 010c83d4e..765d07429 100644 --- a/src/leapfrogai_api/routers/openai/vector_stores.py +++ b/src/leapfrogai_api/routers/openai/vector_stores.py @@ -14,6 +14,7 @@ ListVectorStoresResponse, ModifyVectorStoreRequest, ) +from leapfrogai_api.data.crud_vector_content import CRUDVectorContent from leapfrogai_api.data.crud_vector_store import CRUDVectorStore, FilterVectorStore from leapfrogai_api.data.crud_vector_store_file import ( CRUDVectorStoreFile, @@ -180,8 +181,8 @@ async def delete_vector_store_file( ) -> VectorStoreFileDeleted: """Delete a file in a vector store.""" - vector_store = IndexingService(db=session) - vectors_deleted = await vector_store.adelete_file( + vector_content = CRUDVectorContent(db=session) + vectors_deleted = await vector_content.delete_vectors( vector_store_id=vector_store_id, file_id=file_id ) diff --git a/src/leapfrogai_sdk/pyproject.toml b/src/leapfrogai_sdk/pyproject.toml index 6741059fa..2877c592d 100644 --- a/src/leapfrogai_sdk/pyproject.toml +++ b/src/leapfrogai_sdk/pyproject.toml @@ -3,7 +3,7 @@ name = "leapfrogai-sdk" description = "A tool for building gRPC-based model backends for LeapfrogAI" # x-release-please-start-version -version = "0.9.2" +version = "0.10.0" # x-release-please-end dependencies = [ diff --git a/src/leapfrogai_ui/.env.example b/src/leapfrogai_ui/.env.example index e84c2afb6..0a892df2d 100644 --- a/src/leapfrogai_ui/.env.example +++ b/src/leapfrogai_ui/.env.example @@ -4,18 +4,18 @@ PUBLIC_SUPABASE_ANON_KEY= PUBLIC_DISABLE_KEYCLOAK=false PUBLIC_MESSAGE_LENGTH_LIMIT=10000 + # PRIVATE DYNAMIC DEFAULT_TEMPERATURE=0.1 DEFAULT_SYSTEM_PROMPT="You are a helpful AI assistant created by Defense Unicorns." DEFAULT_MODEL=vllm #for OpenAI it could be: gpt-3.5-turbo LEAPFROGAI_API_BASE_URL=https://leapfrogai-api.uds.dev #for OpenAI it would be: https://api.openai.com -#If specified, app will use OpenAI instead of Leapfrog -OPENAI_API_KEY= - -# SUPABASE AUTH (when running Supabase Locally) +SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://sso.uds.dev/realms/uds SUPABASE_AUTH_KEYCLOAK_CLIENT_ID=uds-supabase SUPABASE_AUTH_KEYCLOAK_SECRET= -SUPABASE_AUTH_EXTERNAL_KEYCLOAK_URL=https://sso.uds.dev/realms/uds + +#If specified, app will use OpenAI instead of Leapfrog +OPENAI_API_KEY= # PLAYWRIGHT USERNAME=user1@test.com diff --git a/src/leapfrogai_ui/.prettierrc b/src/leapfrogai_ui/.prettierrc index 80e397b16..90d842967 100644 --- a/src/leapfrogai_ui/.prettierrc +++ b/src/leapfrogai_ui/.prettierrc @@ -4,6 +4,6 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 100, - "plugins": ["prettier-plugin-svelte"], + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] } diff --git a/src/leapfrogai_ui/README.md b/src/leapfrogai_ui/README.md index 3ef22293c..7b38cdb86 100644 --- a/src/leapfrogai_ui/README.md +++ b/src/leapfrogai_ui/README.md @@ -63,7 +63,7 @@ run the UI outside of UDS on localhost (e.g. for development work), there are so The Supabase UDS package has a ConfigMap called "supabase-auth-default". Add these values to the "GOTRUE_URI_ALLOW_LIST" (no spaces!). This variable may not exist and you will need to add it. Restart the supabase-auth pod after updating the config: - `http://localhost:5173/auth/callback,http://localhost:5173,http://localhost:4173/auth/callback,http://localhost:4173` + `http://localhost:5173/auth/callback,http://localhost:4173/auth/callback` Note - Port 4173 is utilized by Playwright for E2E tests. You do not need this if you are not concerned about Playwright. ###### With Keycloak authentication @@ -180,7 +180,6 @@ The variables that had to be overridden were: ``` [auth] site_url = "http://localhost:5173" -additional_redirect_urls = ["http://localhost:5173/auth/callback"] [auth.external.keycloak] enabled = true @@ -197,9 +196,8 @@ Under a realm in Keycloak that is not the master realm (if using UDS, its "uds") 1. Create a new client (the client ID you use will be used in the env variables below) 2. Turn on "Client Authentication" 3. For "Valid redirect URLs", you need to put: - 1. `http://localhost:5173/auth/callback` (or the URL for the frontend app callback) - 2. `http://127.0.0.1:54321/auth/v1/callback` (or the URL for the Supabase callback, for locally running Supabase, DO NOT USE LOCALHOST, use 127.0.0.1) - 3. Put the same two URLs in for "Web Origins" + 1. `http://127.0.0.1:54321/auth/v1/callback` (or the URL for the Supabase callback, for locally running Supabase, DO NOT USE LOCALHOST, use 127.0.0.1) + 2. Put the same two URLs in for "Web Origins" 4. Copy the Client Secret under the Clients -> Credentials tab and use in the env variables below 5. You can create users under the "Users" tab and either have them verify their email (if you setup SMTP), or manually mark them as verified. @@ -256,23 +254,6 @@ variables will also not do anything because they are only used for locally runni You will instead need to modify the Auth provider settings directly in the Supabase dashboard and set the appropriate redirect URLs and client ID/secret. -If you use the component from @supabase/auth-ui-shared, you must ensure you supply provider and scope props: -ex: - -``` - -``` - If you do not use this component and need to login with a Supabase auth command directly, ensure you provide the "openid" scope with the options parameter. diff --git a/src/leapfrogai_ui/package-lock.json b/src/leapfrogai_ui/package-lock.json index 973169fe4..e4a887fa7 100644 --- a/src/leapfrogai_ui/package-lock.json +++ b/src/leapfrogai_ui/package-lock.json @@ -10,12 +10,6 @@ "dependencies": { "@ai-sdk/openai": "^0.0.36", "@ai-sdk/svelte": "^0.0.17", - "@carbon/colors": "^11.23.0", - "@carbon/layout": "^11.23.0", - "@carbon/themes": "^11.37.0", - "@carbon/type": "^11.28.0", - "@supabase/auth-ui-shared": "^0.1.8", - "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.4", "@sveltejs/vite-plugin-svelte": "^3.1.1", @@ -41,6 +35,7 @@ "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/kit": "^2.5.18", + "@tailwindcss/typography": "^0.5.13", "@testing-library/jest-dom": "^6.4.6", "@testing-library/svelte": "^5.2.0", "@testing-library/user-event": "^14.5.2", @@ -50,26 +45,31 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", - "carbon-components-svelte": "^0.85.0", - "carbon-icons-svelte": "^12.10.0", - "carbon-preprocess-svelte": "^0.11.3", + "autoprefixer": "^10.4.19", "docx": "^8.5.0", "dotenv": "^16.4.5", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.42.0", + "flowbite": "^2.4.1", + "flowbite-svelte": "^0.46.15", + "flowbite-svelte-icons": "^1.6.1", + "husky": "^9.0.11", "jsdom": "^24.1.0", "lodash": "^4.17.21", "msw": "^2.3.1", "otpauth": "^9.3.1", "pdf-lib": "^1.17.1", "playwright": "^1.45.2", + "postcss": "^8.4.38", "pptxgenjs": "^3.12.0", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.6", + "prettier-plugin-tailwindcss": "^0.6.4", "sass": "^1.77.8", "svelte": "^4.2.18", "svelte-check": "^3.8.4", + "tailwindcss": "^3.4.4", "tslib": "^2.6.3", "typescript": "^5.5.3", "vite": "^5.3.4", @@ -135,13 +135,13 @@ } }, "node_modules/@ai-sdk/react": { - "version": "0.0.22", - "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.22.tgz", - "integrity": "sha512-r84qhn08GHtVGjvbveMekP1CeXDs5sIrdDvMaoxhCL8o90HjYTWBSDywuRJU1Jk1uPE/BwZWQu9f5bqaIx6AgA==", + "version": "0.0.25", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.25.tgz", + "integrity": "sha512-79jFOpFRJs0vn9vOTlq0+jSFW8ztTJEXfUYTk+EwCymuGaSdbQQEKEMIDuRpaeOicNjVXZzbkKlQl9bv2l1R/w==", "dependencies": { "@ai-sdk/provider-utils": "1.0.2", - "@ai-sdk/ui-utils": "0.0.14", - "swr": "2.2.0" + "@ai-sdk/ui-utils": "0.0.16", + "swr": "2.2.5" }, "engines": { "node": ">=18" @@ -159,12 +159,32 @@ } } }, - "node_modules/@ai-sdk/solid": { + "node_modules/@ai-sdk/react/node_modules/@ai-sdk/ui-utils": { "version": "0.0.16", - "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.16.tgz", - "integrity": "sha512-rhBTHLY2fx+weF/vrNmYY8S+D6Gcni77YlDDq9kANpNycL99WQo1hRy9fVJ44ICMJCpVrY/kHIl3Jlumdy3oPw==", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.16.tgz", + "integrity": "sha512-DWPj2gPtY+MfEwxPeUZ/pLlKzfvL2u0p2fZLCYDJhJJgFWkvjvKUejRYH9M5uj8zTkZlwi92Gy+vZ20jto5zwQ==", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.2", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/solid": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.18.tgz", + "integrity": "sha512-XHPegleEdbTnb+B1dKsYB8E2oauHr3WP4W4VJ/8xYe7gaJK6Tl88Z8dcIo4szSYgAjJGzOsm0Air3vExTEtO6A==", "dependencies": { - "@ai-sdk/ui-utils": "0.0.14" + "@ai-sdk/ui-utils": "0.0.16" }, "engines": { "node": ">=18" @@ -178,6 +198,26 @@ } } }, + "node_modules/@ai-sdk/solid/node_modules/@ai-sdk/ui-utils": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.16.tgz", + "integrity": "sha512-DWPj2gPtY+MfEwxPeUZ/pLlKzfvL2u0p2fZLCYDJhJJgFWkvjvKUejRYH9M5uj8zTkZlwi92Gy+vZ20jto5zwQ==", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.2", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/@ai-sdk/svelte": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.17.tgz", @@ -220,12 +260,12 @@ } }, "node_modules/@ai-sdk/vue": { - "version": "0.0.17", - "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.17.tgz", - "integrity": "sha512-DoWimMtYNQbqaZZZT00NlfR/G/hEMZLwOZLC58Jp0iIxagJnAcamMJlS1rSR4+vd2UWNk4iPdgsMFdkpDvD9aA==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.20.tgz", + "integrity": "sha512-yI1fIy2hjDy2vCTVcs4vatJ/Pn7UDFUynE6RLOH+K1FRwGEOYLmGO7s3Z59YHe4ZJuPQqWZEjG3dYSGyUJUJYg==", "dependencies": { "@ai-sdk/provider-utils": "0.0.14", - "@ai-sdk/ui-utils": "0.0.14", + "@ai-sdk/ui-utils": "0.0.16", "swrv": "1.0.4" }, "engines": { @@ -273,6 +313,71 @@ } } }, + "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/ui-utils": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.16.tgz", + "integrity": "sha512-DWPj2gPtY+MfEwxPeUZ/pLlKzfvL2u0p2fZLCYDJhJJgFWkvjvKUejRYH9M5uj8zTkZlwi92Gy+vZ20jto5zwQ==", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.2", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.12.tgz", + "integrity": "sha512-oOwPQD8i2Ynpn22cur4sk26FW3mSy6t6/X/K1Ay2yGBKYiSpRyLfObhOrZEGsXDx+3euKy4nEZ193R36NM+tpQ==", + "dependencies": { + "json-schema": "0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/vue/node_modules/@ai-sdk/ui-utils/node_modules/@ai-sdk/provider-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.2.tgz", + "integrity": "sha512-57f6O4OFVNEpI8Z8o+K40tIB3YQiTw+VCql/qrAO9Utq7Ti1o6+X9tvm177DlZJL7ft0Rwzvgy48S9YhrEKgmA==", + "dependencies": { + "@ai-sdk/provider": "0.0.12", + "eventsource-parser": "1.1.2", + "nanoid": "3.3.6", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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", @@ -285,19 +390,19 @@ "node": ">=6.0.0" } }, - "node_modules/@arktype/schema": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@arktype/schema/-/schema-0.1.13.tgz", - "integrity": "sha512-qZjtCAKrnhsixDWsEGJtosWfi4bLpAg4OnnICVYTer/6v5hwlhsdYpYobTSJUc5eiBoI5Ai/kcNfYaQISshY2g==", + "node_modules/@ark/schema": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.2.0.tgz", + "integrity": "sha512-IkNWCSHdjaoemMXpps4uFHEAQzwJPbTAS8K2vcQpk90sa+eNBuPSVyB/81/Qyl1VYW0iX3ceGC5NL/OznQv1jg==", "optional": true, "dependencies": { - "@arktype/util": "0.0.48" + "@ark/util": "0.1.0" } }, - "node_modules/@arktype/util": { - "version": "0.0.48", - "resolved": "https://registry.npmjs.org/@arktype/util/-/util-0.0.48.tgz", - "integrity": "sha512-U5FO5EUAJ4LoYtLSyAMmTf6CEVgslObfSQuua2zoK5Tv2FB3aESVQ3rdLfhuz+coRhlzlynbkmimyoQWwQT+aQ==", + "node_modules/@ark/util": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.1.0.tgz", + "integrity": "sha512-qCLYICQoCy3kEKDVwirQp8qvxhY7NJd8BhhoHaj1l3wCFAk9NUbcDsxAkPStZEMdPI/d7NcbGJe8SWZuRG2twQ==", "optional": true }, "node_modules/@babel/code-frame": { @@ -449,56 +554,14 @@ "statuses": "^2.0.1" } }, - "node_modules/@carbon/colors": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@carbon/colors/-/colors-11.23.0.tgz", - "integrity": "sha512-xXcD064+JPk/tFRN3e3UAOHYpayo9Z6MMcHTt2Vb4U5/+6UUxaDReMPnSL+UNSrf75d4epomB+GrpN0tbmgMCA==", - "hasInstallScript": true, - "dependencies": { - "@ibm/telemetry-js": "^1.5.0" - } - }, - "node_modules/@carbon/grid": { - "version": "11.24.0", - "resolved": "https://registry.npmjs.org/@carbon/grid/-/grid-11.24.0.tgz", - "integrity": "sha512-1HYS5FhLqLBBsmHA/oWikRXVq0C639DUZf0VGw+Dl7Nl7UasHDzFkErN0W3pNyMXPVr/rxSZV3ihJJTBsgVuhg==", - "hasInstallScript": true, - "dependencies": { - "@carbon/layout": "^11.23.0", - "@ibm/telemetry-js": "^1.5.0" - } - }, - "node_modules/@carbon/layout": { - "version": "11.23.0", - "resolved": "https://registry.npmjs.org/@carbon/layout/-/layout-11.23.0.tgz", - "integrity": "sha512-7YFTFG9mqtvFp67h+qO2z2BG7QmbHc0jVgUevwtOHSCdVHRwhYqEFNGCCE4+MhMHetUjl/z3ut4rNfHleoyOWg==", - "hasInstallScript": true, - "dependencies": { - "@ibm/telemetry-js": "^1.5.0" - } - }, - "node_modules/@carbon/themes": { - "version": "11.37.0", - "resolved": "https://registry.npmjs.org/@carbon/themes/-/themes-11.37.0.tgz", - "integrity": "sha512-nGsU4x13190Mrm1beb6Oy9Vacv9NNKhRSX/9DYXa6uB9anMy14vzZMLBZ5L1ubL6Eh8GRD06y7TO9lq2xqEkvw==", - "hasInstallScript": true, - "dependencies": { - "@carbon/colors": "^11.23.0", - "@carbon/layout": "^11.23.0", - "@carbon/type": "^11.28.0", - "@ibm/telemetry-js": "^1.5.0", - "color": "^4.0.0" - } - }, - "node_modules/@carbon/type": { - "version": "11.28.0", - "resolved": "https://registry.npmjs.org/@carbon/type/-/type-11.28.0.tgz", - "integrity": "sha512-c0iJXuZgsCr8WARe8h3aMzdYuxM7pW81b+j6AP2Ra2n+/13jK0zlDLLel4pp3U0UAd/qSrlk6PKlwVLH76eLEg==", - "hasInstallScript": true, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, "dependencies": { - "@carbon/grid": "^11.24.0", - "@carbon/layout": "^11.23.0", - "@ibm/telemetry-js": "^1.5.0" + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" } }, "node_modules/@commitlint/cli": { @@ -1224,6 +1287,31 @@ "npm": ">=6.14.13" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", + "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==", + "dev": true, + "dependencies": { + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz", + "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==", + "dev": true, + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==", + "dev": true + }, "node_modules/@gcornut/valibot-json-schema": { "version": "0.31.0", "resolved": "https://registry.npmjs.org/@gcornut/valibot-json-schema/-/valibot-json-schema-0.31.0.tgz", @@ -1317,14 +1405,6 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, - "node_modules/@ibm/telemetry-js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@ibm/telemetry-js/-/telemetry-js-1.5.2.tgz", - "integrity": "sha512-KyvaHbiNMDtz2k/9DltkK3YkWTyvz8y7Pq1sQ4cnXDMzHiEatOyxw3zZgK9li80tgUOYMQLck9DLewEuhvtg7w==", - "bin": { - "ibmtelemetry": "dist/collect.js" - } - }, "node_modules/@inquirer/confirm": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.8.tgz", @@ -1575,15 +1655,6 @@ "@lit-labs/ssr-dom-shim": "^1.2.0" } }, - "node_modules/@mswjs/cookies": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@mswjs/cookies/-/cookies-1.1.0.tgz", - "integrity": "sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/@mswjs/interceptors": { "version": "0.29.1", "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.29.1.tgz", @@ -1707,12 +1778,12 @@ } }, "node_modules/@playwright/test": { - "version": "1.45.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.2.tgz", - "integrity": "sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==", + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.3.tgz", + "integrity": "sha512-UKF4XsBfy+u3MFWEH44hva1Q8Da28G6RFtR2+5saw+jgAFQV5yYnB1fu68Mz7fO+5GJF3wgwAIs0UelU8TxFrA==", "dev": true, "dependencies": { - "playwright": "1.45.2" + "playwright": "1.45.3" }, "bin": { "playwright": "cli.js" @@ -1726,6 +1797,16 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==" }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@poppinss/macroable": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.0.2.tgz", @@ -2041,9 +2122,9 @@ "optional": true }, "node_modules/@sinclair/typebox": { - "version": "0.32.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.34.tgz", - "integrity": "sha512-a3Z3ytYl6R/+7ldxx04PO1semkwWlX/8pTqxsPw4quIcIXDFPZhOc1Wx8azWmkU26ccK3mHwcWenn0avNgAKQg==", + "version": "0.32.35", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.32.35.tgz", + "integrity": "sha512-Ul3YyOTU++to8cgNkttakC0dWvpERr6RYoHO2W47DLbFvrwBDJUY31B1sImH6JZSYc4Kt4PyHtoPNu+vL2r2dA==", "optional": true }, "node_modules/@sodaru/yup-to-json-schema": { @@ -2052,11 +2133,6 @@ "integrity": "sha512-lWb0Wiz8KZ9ip/dY1eUqt7fhTPmL24p6Hmv5Fd9pzlzAdw/YNcWZr+tiCT4oZ4Zyxzi9+1X4zv82o7jYvcFxYA==", "optional": true }, - "node_modules/@stitches/core": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", - "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" - }, "node_modules/@supabase/auth-js": { "version": "2.64.4", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.64.4.tgz", @@ -2065,35 +2141,6 @@ "@supabase/node-fetch": "^2.6.14" } }, - "node_modules/@supabase/auth-ui-shared": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@supabase/auth-ui-shared/-/auth-ui-shared-0.1.8.tgz", - "integrity": "sha512-ouQ0DjKcEFg+0gZigFIEgu01V3e6riGZPzgVD0MJsCBNsMsiDT74+GgCEIElMUpTGkwSja3xLwdFRFgMNFKcjg==", - "peerDependencies": { - "@supabase/supabase-js": "^2.21.0" - } - }, - "node_modules/@supabase/auth-ui-svelte": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@supabase/auth-ui-svelte/-/auth-ui-svelte-0.2.9.tgz", - "integrity": "sha512-oc+SRS7ykc5FCssoqT0IiK5KF/obwnWko5ePaaMTDwUEQOavv+O8/poAh2lRTKRJCqJMqOOpMS/lUE6pHcma3g==", - "dependencies": { - "@stitches/core": "^1.2.8", - "@supabase/auth-ui-shared": "0.1.8", - "svelte": "^3.55.1" - }, - "peerDependencies": { - "@supabase/supabase-js": "^2.21.0" - } - }, - "node_modules/@supabase/auth-ui-svelte/node_modules/svelte": { - "version": "3.59.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz", - "integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==", - "engines": { - "node": ">= 8" - } - }, "node_modules/@supabase/functions-js": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.1.tgz", @@ -2278,10 +2325,38 @@ "vite": "^5.0.0" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", + "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", + "dev": true, + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@testing-library/dom": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.1.0.tgz", - "integrity": "sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==", + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.2.tgz", + "integrity": "sha512-0bxIdP9mmPiOJ6wHLj8bdJRq+51oddObeCGdEf6PNEhYd93ZYAN+lPRnEOVFtheVwDM7+p+tza3LAQgp0PTudg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.10.4", @@ -2341,9 +2416,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.6.tgz", - "integrity": "sha512-8qpnGVincVDLEcQXWaHOf6zmlbwTKc6Us6PPu4CRnPXCzo2OGBS5cwgMMOWdxDpEz1mkbvXHpEy99M5Yvt682w==", + "version": "6.4.8", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.8.tgz", + "integrity": "sha512-JD0G+Zc38f5MBHA4NgxQMR5XtO5Jx9g86jqturNTt2WUfRmLDIY7iKkWHDCCTiDuFMre6nxAD5wHw9W5kI4rGw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.4.0", @@ -2359,30 +2434,6 @@ "node": ">=14", "npm": ">=6", "yarn": ">=1" - }, - "peerDependencies": { - "@jest/globals": ">= 28", - "@types/bun": "latest", - "@types/jest": ">= 28", - "jest": ">= 28", - "vitest": ">= 0.32" - }, - "peerDependenciesMeta": { - "@jest/globals": { - "optional": true - }, - "@types/bun": { - "optional": true - }, - "@types/jest": { - "optional": true - }, - "jest": { - "optional": true - }, - "vitest": { - "optional": true - } } }, "node_modules/@testing-library/jest-dom/node_modules/ansi-styles": { @@ -2578,6 +2629,12 @@ "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", "dev": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2610,16 +2667,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", + "integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/type-utils": "7.17.0", + "@typescript-eslint/utils": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2643,15 +2700,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", - "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", + "integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/typescript-estree": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "debug": "^4.3.4" }, "engines": { @@ -2671,13 +2728,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", + "integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2688,13 +2745,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", + "integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/typescript-estree": "7.17.0", + "@typescript-eslint/utils": "7.17.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2715,9 +2772,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", + "integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2728,13 +2785,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", + "integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/visitor-keys": "7.17.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2756,15 +2813,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", + "integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "@typescript-eslint/scope-manager": "7.17.0", + "@typescript-eslint/types": "7.17.0", + "@typescript-eslint/typescript-estree": "7.17.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2778,12 +2835,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", + "integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.16.1", + "@typescript-eslint/types": "7.17.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2829,13 +2886,13 @@ } }, "node_modules/@vitest/expect": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.3.tgz", - "integrity": "sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.4.tgz", + "integrity": "sha512-39jr5EguIoanChvBqe34I8m1hJFI4+jxvdOpD7gslZrVQBKhh8H9eD7J/LJX4zakrw23W+dITQTDqdt43xVcJw==", "dev": true, "dependencies": { - "@vitest/spy": "2.0.3", - "@vitest/utils": "2.0.3", + "@vitest/spy": "2.0.4", + "@vitest/utils": "2.0.4", "chai": "^5.1.1", "tinyrainbow": "^1.2.0" }, @@ -2844,9 +2901,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.3.tgz", - "integrity": "sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz", + "integrity": "sha512-RYZl31STbNGqf4l2eQM1nvKPXE0NhC6Eq0suTTePc4mtMQ1Fn8qZmjV4emZdEdG2NOWGKSCrHZjmTqDCDoeFBw==", "dev": true, "dependencies": { "tinyrainbow": "^1.2.0" @@ -2856,12 +2913,12 @@ } }, "node_modules/@vitest/runner": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.3.tgz", - "integrity": "sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.4.tgz", + "integrity": "sha512-Gk+9Su/2H2zNfNdeJR124gZckd5st4YoSuhF1Rebi37qTXKnqYyFCd9KP4vl2cQHbtuVKjfEKrNJxHHCW8thbQ==", "dev": true, "dependencies": { - "@vitest/utils": "2.0.3", + "@vitest/utils": "2.0.4", "pathe": "^1.1.2" }, "funding": { @@ -2869,12 +2926,12 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.3.tgz", - "integrity": "sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.4.tgz", + "integrity": "sha512-or6Mzoz/pD7xTvuJMFYEtso1vJo1S5u6zBTinfl+7smGUhqybn6VjzCDMhmTyVOFWwkCMuNjmNNxnyXPgKDoPw==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.3", + "@vitest/pretty-format": "2.0.4", "magic-string": "^0.30.10", "pathe": "^1.1.2" }, @@ -2883,9 +2940,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.3.tgz", - "integrity": "sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.4.tgz", + "integrity": "sha512-uTXU56TNoYrTohb+6CseP8IqNwlNdtPwEO0AWl+5j7NelS6x0xZZtP0bDWaLvOfUbaYwhhWp1guzXUxkC7mW7Q==", "dev": true, "dependencies": { "tinyspy": "^3.0.0" @@ -2895,12 +2952,12 @@ } }, "node_modules/@vitest/utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.3.tgz", - "integrity": "sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.4.tgz", + "integrity": "sha512-Zc75QuuoJhOBnlo99ZVUkJIuq4Oj0zAkrQ2VzCqNCx6wAwViHEh5Fnp4fiJTE9rA+sAoXRf00Z9xGgfEzV6fzQ==", "dev": true, "dependencies": { - "@vitest/pretty-format": "2.0.3", + "@vitest/pretty-format": "2.0.4", "estree-walker": "^3.0.3", "loupe": "^3.1.1", "tinyrainbow": "^1.2.0" @@ -2919,39 +2976,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.32.tgz", - "integrity": "sha512-8tCVWkkLe/QCWIsrIvExUGnhYCAOroUs5dzhSoKL5w4MJS8uIYiou+pOPSVIOALOQ80B0jBs+Ri+kd5+MBnCDw==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.33.tgz", + "integrity": "sha512-MoIREbkdPQlnGfSKDMgzTqzqx5nmEjIc0ydLVYlTACGBsfvOJ4tHSbZXKVF536n6fB+0eZaGEOqsGThPpdvF5A==", "peer": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.32", + "@vue/shared": "3.4.33", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.32.tgz", - "integrity": "sha512-PbSgt9KuYo4fyb90dynuPc0XFTfFPs3sCTbPLOLlo+PrUESW1gn/NjSsUvhR+mI2AmmEzexwYMxbHDldxSOr2A==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.33.tgz", + "integrity": "sha512-GzB8fxEHKw0gGet5BKlpfXEqoBnzSVWwMnT+dc25wE7pFEfrU/QsvjZMP9rD4iVXHBBoemTct8mN0GJEI6ZX5A==", "peer": true, "dependencies": { - "@vue/compiler-core": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-core": "3.4.33", + "@vue/shared": "3.4.33" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.32.tgz", - "integrity": "sha512-STy9im/WHfaguJnfKjjVpMHukxHUrOKjm2vVCxiojQJyo3Sb6Os8SMXBr/MI+ekpstEGkDONfqAQoSbZhspLYw==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.33.tgz", + "integrity": "sha512-7rk7Vbkn21xMwIUpHQR4hCVejwE6nvhBOiDgoBcR03qvGqRKA7dCBSsHZhwhYUsmjlbJ7OtD5UFIyhP6BY+c8A==", "peer": true, "dependencies": { "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.32", - "@vue/compiler-dom": "3.4.32", - "@vue/compiler-ssr": "3.4.32", - "@vue/shared": "3.4.32", + "@vue/compiler-core": "3.4.33", + "@vue/compiler-dom": "3.4.33", + "@vue/compiler-ssr": "3.4.33", + "@vue/shared": "3.4.33", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.39", @@ -2959,65 +3016,71 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.32.tgz", - "integrity": "sha512-nyu/txTecF6DrxLrpLcI34xutrvZPtHPBj9yRoPxstIquxeeyywXpYZrQMsIeDfBhlw1abJb9CbbyZvDw2kjdg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.33.tgz", + "integrity": "sha512-0WveC9Ai+eT/1b6LCV5IfsufBZ0HP7pSSTdDjcuW302tTEgoBw8rHVHKPbGUtzGReUFCRXbv6zQDDgucnV2WzQ==", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-dom": "3.4.33", + "@vue/shared": "3.4.33" } }, "node_modules/@vue/reactivity": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.32.tgz", - "integrity": "sha512-1P7QvghAzhSIWmiNmh4MNkLVjr2QTNDcFv2sKmytEWhR6t7BZzNicgm5ENER4uU++wbWxgRh/pSEYgdI3MDcvg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.33.tgz", + "integrity": "sha512-B24QIelahDbyHipBgbUItQblbd4w5HpG3KccL+YkGyo3maXyS253FzcTR3pSz739OTphmzlxP7JxEMWBpewilA==", "peer": true, "dependencies": { - "@vue/shared": "3.4.32" + "@vue/shared": "3.4.33" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.32.tgz", - "integrity": "sha512-FxT2dTHUs1Hki8Ui/B1Hu339mx4H5kRJooqrNM32tGUHBPStJxwMzLIRbeGO/B1NMplU4Pg9fwOqrJtrOzkdfA==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.33.tgz", + "integrity": "sha512-6wavthExzT4iAxpe8q37/rDmf44nyOJGISJPxCi9YsQO+8w9v0gLCFLfH5TzD1V1AYrTAdiF4Y1cgUmP68jP6w==", "peer": true, "dependencies": { - "@vue/reactivity": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/reactivity": "3.4.33", + "@vue/shared": "3.4.33" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.32.tgz", - "integrity": "sha512-Xz9G+ZViRyPFQtRBCPFkhMzKn454ihCPMKUiacNaUhuTIXvyfkAq8l89IZ/kegFVyw/7KkJGRGqYdEZrf27Xsg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.33.tgz", + "integrity": "sha512-iHsMCUSFJ+4z432Bn9kZzHX+zOXa6+iw36DaVRmKYZpPt9jW9riF32SxNwB124i61kp9+AZtheQ/mKoJLerAaQ==", "peer": true, "dependencies": { - "@vue/reactivity": "3.4.32", - "@vue/runtime-core": "3.4.32", - "@vue/shared": "3.4.32", + "@vue/reactivity": "3.4.33", + "@vue/runtime-core": "3.4.33", + "@vue/shared": "3.4.33", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.32.tgz", - "integrity": "sha512-3c4rd0522Ao8hKjzgmUAbcjv2mBnvnw0Ld2f8HOMCuWJZjYie/p8cpIoYJbeP0VV2JYmrJJMwGQDO5RH4iQ30A==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.33.tgz", + "integrity": "sha512-jTH0d6gQcaYideFP/k0WdEu8PpRS9MF8d0b6SfZzNi+ap972pZ0TNIeTaESwdOtdY0XPVj54XEJ6K0wXxir4fw==", "peer": true, "dependencies": { - "@vue/compiler-ssr": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-ssr": "3.4.33", + "@vue/shared": "3.4.33" }, "peerDependencies": { - "vue": "3.4.32" + "vue": "3.4.33" } }, "node_modules/@vue/shared": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.32.tgz", - "integrity": "sha512-ep4mF1IVnX/pYaNwxwOpJHyBtOMKWoKZMbnUyd+z0udqIxLUh7YCCd/JfDna8aUrmnG9SFORyIq2HzEATRrQsg==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.33.tgz", + "integrity": "sha512-aoRY0jQk3A/cuvdkodTrM4NMfxco8n55eG4H7ML/CRy7OryHfiqvug4xrCBBMbbN+dvXAetDDwZW9DXWWjBntA==", "peer": true }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", + "dev": true + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -3081,17 +3144,17 @@ } }, "node_modules/ai": { - "version": "3.2.27", - "resolved": "https://registry.npmjs.org/ai/-/ai-3.2.27.tgz", - "integrity": "sha512-J0SCozj1c8ZTprYVAH3LL1XYR5TRT6oGsXZ5leSqXeEo1SWBx6rFEqDKZLFskLvOf3Ta/AG6EDDrP1EErwapMQ==", + "version": "3.2.32", + "resolved": "https://registry.npmjs.org/ai/-/ai-3.2.32.tgz", + "integrity": "sha512-1TmEY6jkgouP/OYLdt4rBZAUg6mFSfwLPUEwk0D7pLKy3tYB3OYz/XvZ5X8NShG3wNTv7pMzE8znM0vg5RIdoA==", "dependencies": { "@ai-sdk/provider": "0.0.12", "@ai-sdk/provider-utils": "1.0.2", - "@ai-sdk/react": "0.0.22", - "@ai-sdk/solid": "0.0.16", - "@ai-sdk/svelte": "0.0.17", - "@ai-sdk/ui-utils": "0.0.14", - "@ai-sdk/vue": "0.0.17", + "@ai-sdk/react": "0.0.25", + "@ai-sdk/solid": "0.0.18", + "@ai-sdk/svelte": "0.0.19", + "@ai-sdk/ui-utils": "0.0.16", + "@ai-sdk/vue": "0.0.20", "@opentelemetry/api": "1.9.0", "eventsource-parser": "1.1.2", "json-schema": "0.4.0", @@ -3125,6 +3188,47 @@ } } }, + "node_modules/ai/node_modules/@ai-sdk/svelte": { + "version": "0.0.19", + "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.19.tgz", + "integrity": "sha512-eRBNXUshfwzYm3iaPCIZeV+XHZ0Hkrgj+V0Jy3bGvVzDX0cAs0EAxGPqYFArLFhuEBqiTQrW3QYL540GWYPl6g==", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.2", + "@ai-sdk/ui-utils": "0.0.16", + "sswr": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "svelte": "^3.0.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/ai/node_modules/@ai-sdk/ui-utils": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.16.tgz", + "integrity": "sha512-DWPj2gPtY+MfEwxPeUZ/pLlKzfvL2u0p2fZLCYDJhJJgFWkvjvKUejRYH9M5uj8zTkZlwi92Gy+vZ20jto5zwQ==", + "dependencies": { + "@ai-sdk/provider-utils": "1.0.2", + "secure-json-parse": "2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, "node_modules/ajv": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", @@ -3188,6 +3292,12 @@ "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==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -3201,6 +3311,27 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.50.0.tgz", + "integrity": "sha512-LJT1PNAm+NoIU3aogL2P+ViC0y/Cjik54FdzzGV54UNnGQLBoLe5ok3fxsJDTgyez45BGYT8gqNpYKqhdfy5sg==", + "dev": true, + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, + "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==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3215,13 +3346,13 @@ } }, "node_modules/arktype": { - "version": "2.0.0-dev.21", - "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.0.0-dev.21.tgz", - "integrity": "sha512-dgHCjb3FK4BGvG2LuXqgdWXstbFmiYowSy0jiKnyk4KVcMT5DyIJ9d1nbQM3ztiAL3hIPmPdkmpfxUqR+BoOBQ==", + "version": "2.0.0-beta.0", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.0.0-beta.0.tgz", + "integrity": "sha512-fE3ssMiXjr/bLqFPzlDhRlXngdyHQreu7p7i8+dtcY1CA+f8WrVUcue6JxywhnqEJXPG4HOcIwQcC+q4VfeUMQ==", "optional": true, "dependencies": { - "@arktype/schema": "0.1.13", - "@arktype/util": "0.0.48" + "@ark/schema": "0.2.0", + "@ark/util": "0.1.0" } }, "node_modules/array-ify": { @@ -3253,13 +3384,50 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/axobject-query": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", - "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", - "dependencies": { - "dequal": "^2.0.3" - } + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "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/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "dependencies": { + "dequal": "^2.0.3" + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -3300,6 +3468,38 @@ "node": ">=8" } }, + "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==", + "dev": true, + "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/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -3357,40 +3557,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/carbon-components-svelte": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/carbon-components-svelte/-/carbon-components-svelte-0.85.0.tgz", - "integrity": "sha512-inJs4RE7ubiOafuiDkUkiV/np8ab63nzDNp+x2k+7kUhewtrYNTTyMHsMM/y2eQS042riXLzAxNsqF7MXm+kfA==", + "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==", "dev": true, - "hasInstallScript": true, - "dependencies": { - "@ibm/telemetry-js": "^1.2.1", - "flatpickr": "4.6.9" + "engines": { + "node": ">= 6" } }, - "node_modules/carbon-icons-svelte": { - "version": "12.10.0", - "resolved": "https://registry.npmjs.org/carbon-icons-svelte/-/carbon-icons-svelte-12.10.0.tgz", - "integrity": "sha512-GOPRBVm3klnaLksVFRvECKOvAXpdjF5FBKo/knQ/WG3LTehg6zvVButW+MdYZtxrtpg8Iqt95BRDmGBimiweWA==", - "dev": true + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "dev": true, + "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/carbon-pictograms-svelte": { "version": "12.11.0", "resolved": "https://registry.npmjs.org/carbon-pictograms-svelte/-/carbon-pictograms-svelte-12.11.0.tgz", "integrity": "sha512-JWu/wcaq2nCWvre6FACGk2mXv1yEUTSoPfX+ioRpZlsDY4pFvCQOep8ozOLh/zJ1682AzkLbqv24S22fkKWDvQ==" }, - "node_modules/carbon-preprocess-svelte": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/carbon-preprocess-svelte/-/carbon-preprocess-svelte-0.11.3.tgz", - "integrity": "sha512-gzzXNFUCrhYUNWxcuPRACWzDw/1CyyEUMrovXuS5jOTxmUC+m6XlElqbW8BR8+dznzFbwH/8dTfTuCbcixI3/A==", - "dev": true, - "dependencies": { - "estree-walker": "^2.0.2", - "magic-string": "^0.30.8", - "postcss": "^8.4.36", - "postcss-discard-empty": "^6.0.3" - } - }, "node_modules/cfb": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", @@ -3497,6 +3697,11 @@ "node": ">= 12" } }, + "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": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -3569,18 +3774,6 @@ "node": ">=0.8" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3597,15 +3790,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3617,6 +3801,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -4025,6 +4218,12 @@ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==" }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", @@ -4046,7 +4245,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "optional": true + "devOptional": true }, "node_modules/doctrine": { "version": "3.0.0", @@ -4135,6 +4334,12 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/electron-to-chromium": { + "version": "1.4.829", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", + "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", + "dev": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4339,9 +4544,9 @@ } }, "node_modules/eslint-plugin-svelte": { - "version": "2.42.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.42.0.tgz", - "integrity": "sha512-mHP6z0DWq97KZvoQcApZHdF9m9epcDV/ICKufeEH18Vh+8vl7S+gwt8WdUohEqKNVMuXRkbvy1suMcVvUDiOGw==", + "version": "2.43.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.43.0.tgz", + "integrity": "sha512-REkxQWvg2pp7QVLxQNa+dJ97xUqRe7Y2JJbSWkHSuszu0VcblZtXkPBPckkivk99y5CdLw4slqfPylL2d/X4jQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -4354,7 +4559,7 @@ "postcss-safe-parser": "^6.0.0", "postcss-selector-parser": "^6.1.0", "semver": "^7.6.2", - "svelte-eslint-parser": "^0.40.0" + "svelte-eslint-parser": "^0.41.0" }, "engines": { "node": "^14.17.0 || >=16.0.0" @@ -4364,7 +4569,7 @@ }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.181" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" }, "peerDependenciesMeta": { "svelte": { @@ -4787,18 +4992,63 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flatpickr": { - "version": "4.6.9", - "resolved": "https://registry.npmjs.org/flatpickr/-/flatpickr-4.6.9.tgz", - "integrity": "sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw==", - "dev": true - }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/flowbite": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.4.1.tgz", + "integrity": "sha512-I++vDsSOOlzHNuxY2OcFMNVC4CNzpPU2K14YHJ81cYrANXdzgizqniMB/1KQ219x8fqw+S0msY9Q45ZSXDqAPw==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.3", + "flowbite-datepicker": "^1.3.0", + "mini-svg-data-uri": "^1.4.3" + } + }, + "node_modules/flowbite-datepicker": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flowbite-datepicker/-/flowbite-datepicker-1.3.0.tgz", + "integrity": "sha512-CLVqzuoE2vkUvWYK/lJ6GzT0be5dlTbH3uuhVwyB67+PjqJWABm2wv68xhBf5BqjpBxvTSQ3mrmLHpPJ2tvrSQ==", + "dev": true, + "dependencies": { + "@rollup/plugin-node-resolve": "^15.2.3", + "flowbite": "^2.0.0" + } + }, + "node_modules/flowbite-svelte": { + "version": "0.46.15", + "resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.46.15.tgz", + "integrity": "sha512-xWhyLDez/gafTAmQayPMPKmWj1BAoq80SoA48yHZ12Wk3Vu3hFrELLUZf0UksxnjQL9hMOKRUlYl3/IH6pHwnQ==", + "dev": true, + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "apexcharts": "^3.49.2", + "flowbite": "^2.4.1", + "tailwind-merge": "^2.3.0" + }, + "engines": { + "node": ">=18.0.0", + "pnpm": ">=8.0.0" + }, + "peerDependencies": { + "svelte": "^3.55.1 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/flowbite-svelte-icons": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/flowbite-svelte-icons/-/flowbite-svelte-icons-1.6.1.tgz", + "integrity": "sha512-Kw/7BzA6fqlFq7tBNudwX0KVU4cbyyXcMcgHTraMwGBtvBQan0RKMbvWwqm4JZNvLGAvRv1BM2EF7rzo/oam1Q==", + "dev": true, + "peerDependencies": { + "svelte": "^3.54.0 || ^4.0.0 || ^5.0.0", + "tailwind-merge": "^2.0.0", + "tailwindcss": "^3.3.2" + } + }, "node_modules/foreground-child": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", @@ -4862,6 +5112,19 @@ "node": ">=0.8" } }, + "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==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5149,6 +5412,21 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "dev": true, + "bin": { + "husky": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -5787,6 +6065,12 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", @@ -5990,10 +6274,19 @@ "node": ">=4" } }, + "node_modules/mini-svg-data-uri": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", + "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", + "dev": true, + "bin": { + "mini-svg-data-uri": "cli.js" + } + }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -6057,16 +6350,16 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/msw": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.1.tgz", - "integrity": "sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.3.4.tgz", + "integrity": "sha512-sHMlwrajgmZSA2l1o7qRSe+azm/I+x9lvVVcOxAzi4vCtH8uVPJk1K5BQYDkzGl+tt0RvM9huEXXdeGrgcc79g==", "dev": true, "hasInstallScript": true, "dependencies": { "@bundled-es-modules/cookie": "^2.0.0", "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", "@inquirer/confirm": "^3.0.0", - "@mswjs/cookies": "^1.1.0", "@mswjs/interceptors": "^0.29.0", "@open-draft/until": "^2.1.0", "@types/cookie": "^0.6.0", @@ -6163,6 +6456,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -6223,6 +6527,12 @@ } } }, + "node_modules/node-releases": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", + "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "dev": true + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6232,6 +6542,15 @@ "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -6276,6 +6595,24 @@ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" }, + "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6301,9 +6638,9 @@ } }, "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==", + "version": "4.53.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.53.0.tgz", + "integrity": "sha512-XoMaJsSLuedW5eoMEMmZbdNoXgML3ujcU5KfwRnC6rnbmZkHE2Q4J/SArwhqCxQRqJwHnQUj1LpiROmKPExZJA==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", @@ -6584,13 +6921,31 @@ "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==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/playwright": { - "version": "1.45.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.2.tgz", - "integrity": "sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==", + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.3.tgz", + "integrity": "sha512-QhVaS+lpluxCaioejDZ95l4Y4jSFCsBvl2UZkpeXlzxmqS+aABr5c82YmfMHrL6x27nvrvykJAFpkzT2eWdJww==", "dev": true, "dependencies": { - "playwright-core": "1.45.2" + "playwright-core": "1.45.3" }, "bin": { "playwright": "cli.js" @@ -6603,9 +6958,9 @@ } }, "node_modules/playwright-core": { - "version": "1.45.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.2.tgz", - "integrity": "sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==", + "version": "1.45.3", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.3.tgz", + "integrity": "sha512-+ym0jNbcjikaOwwSZycFbwkWgfruWvYlJfThKYAlImbxUgdWFO2oW70ojPm4OpE4t6TAo2FY/smM+hpVTtkhDA==", "dev": true, "bin": { "playwright-core": "cli.js" @@ -6641,16 +6996,40 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "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==", "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, "engines": { - "node": "^14 || ^16 || >=18.0" + "node": ">=14.0.0" }, "peerDependencies": { - "postcss": "^8.4.31" + "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==", + "dev": true, + "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-load-config": { @@ -6682,6 +7061,25 @@ } } }, + "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==", + "dev": true, + "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-safe-parser": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", @@ -6737,6 +7135,12 @@ "node": ">=4" } }, + "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==", + "dev": true + }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -6809,6 +7213,80 @@ "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz", + "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -6907,6 +7385,15 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": 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==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -7344,19 +7831,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -7582,6 +8056,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dev": true, + "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/superstruct": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz", @@ -7662,9 +8158,9 @@ } }, "node_modules/svelte-eslint-parser": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.40.0.tgz", - "integrity": "sha512-M+v1HhC5T1WKYVxWexUCS4o6oIBS88XKzOZuhl2ew+eGxol7eC21e+VE8TC4rXJ3iT3iXT0qlZsZcpKjVo5/zQ==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.41.0.tgz", + "integrity": "sha512-L6f4hOL+AbgfBIB52Z310pg1d2QjRqm7wy3kI1W6hhdhX5bvu7+f0R6w4ykp5HoDdzq+vGhIJmsisaiJDGmVfA==", "dev": true, "dependencies": { "eslint-scope": "^7.2.2", @@ -7680,7 +8176,7 @@ "url": "https://github.com/sponsors/ota-meshi" }, "peerDependencies": { - "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.181" + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.191" }, "peerDependenciesMeta": { "svelte": { @@ -7778,9 +8274,9 @@ } }, "node_modules/sveltekit-superforms": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.16.0.tgz", - "integrity": "sha512-t5aZyMCXyahgvn7VAJ7l9S+wAv9YyMAZHYQ6gcGVZ6ecr4/DMMD2r3ajGCj1h1dWaMQ3w5zVw+SqHSAkbHsWVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.16.1.tgz", + "integrity": "sha512-RNBdN43xge/ADmc3s7+pfdnRGuZ9gZiqpX6VKAQCnCI+ICc5rrPv5idYbx4iuY1Ia0lRMAq1hP0x2oHaPjB+Kg==", "funding": [ { "type": "github", @@ -7807,7 +8303,7 @@ "@sinclair/typebox": "^0.32.34", "@sodaru/yup-to-json-schema": "^2.0.1", "@vinejs/vine": "^1.8.0", - "arktype": "2.0.0-dev.21", + "arktype": "2.0.0-beta.0", "joi": "^17.13.3", "json-schema-to-ts": "^3.1.0", "superstruct": "^2.0.2", @@ -7821,7 +8317,7 @@ "@sinclair/typebox": ">=0.32.30 <1", "@sveltejs/kit": "1.x || 2.x", "@vinejs/vine": "^1.8.0", - "arktype": ">=2.0.0-dev.21", + "arktype": ">=2.0.0-beta.0", "joi": "^17.13.1", "superstruct": "^2.0.2", "svelte": "3.x || 4.x || >=5.0.0-next.51", @@ -7868,11 +8364,103 @@ "zod": "^3.23.3" } }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dev": true, + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dev": true, + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dev": true, + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==", + "dev": true + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dev": true, + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dev": true, + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dev": true, + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dev": true, + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/swr": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.0.tgz", - "integrity": "sha512-AjqHOv2lAhkuUdIiBu9xbuettzAzWXmCEcLONNKJRba87WAefz8Ca9d6ds/SzrPc235n1IxWYdhJ2zF3MNUaoQ==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", "dependencies": { + "client-only": "^0.0.1", "use-sync-external-store": "^1.2.0" }, "peerDependencies": { @@ -7897,6 +8485,112 @@ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" }, + "node_modules/tailwind-merge": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", + "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", + "integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.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/tailwindcss/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==", + "dev": true, + "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/tailwindcss/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==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/tailwindcss/node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/text-extensions": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", @@ -7915,6 +8609,27 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -8046,6 +8761,12 @@ "node": ">=14.13.1" } }, + "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==", + "dev": true + }, "node_modules/tslib": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", @@ -8076,9 +8797,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", - "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -8118,6 +8839,36 @@ "node": ">= 4.0.0" } }, + "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==", + "dev": true, + "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", @@ -8232,9 +8983,9 @@ } }, "node_modules/vite-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.3.tgz", - "integrity": "sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.4.tgz", + "integrity": "sha512-ZpJVkxcakYtig5iakNeL7N3trufe3M6vGuzYAr4GsbCTwobDeyPJpE4cjDhhPluv8OvQCFzu2LWp6GkoKRITXA==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -8662,18 +9413,18 @@ } }, "node_modules/vitest": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.3.tgz", - "integrity": "sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.4.tgz", + "integrity": "sha512-luNLDpfsnxw5QSW4bISPe6tkxVvv5wn2BBs/PuDRkhXZ319doZyLOBr1sjfB5yCEpTiU7xCAdViM8TNVGPwoog==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.3.0", - "@vitest/expect": "2.0.3", - "@vitest/pretty-format": "^2.0.3", - "@vitest/runner": "2.0.3", - "@vitest/snapshot": "2.0.3", - "@vitest/spy": "2.0.3", - "@vitest/utils": "2.0.3", + "@vitest/expect": "2.0.4", + "@vitest/pretty-format": "^2.0.4", + "@vitest/runner": "2.0.4", + "@vitest/snapshot": "2.0.4", + "@vitest/spy": "2.0.4", + "@vitest/utils": "2.0.4", "chai": "^5.1.1", "debug": "^4.3.5", "execa": "^8.0.1", @@ -8684,8 +9435,8 @@ "tinypool": "^1.0.0", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", - "vite-node": "2.0.3", - "why-is-node-running": "^2.2.2" + "vite-node": "2.0.4", + "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" @@ -8699,8 +9450,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "2.0.3", - "@vitest/ui": "2.0.3", + "@vitest/browser": "2.0.4", + "@vitest/ui": "2.0.4", "happy-dom": "*", "jsdom": "*" }, @@ -8726,16 +9477,16 @@ } }, "node_modules/vue": { - "version": "3.4.32", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.32.tgz", - "integrity": "sha512-9mCGIAi/CAq7GtaLLLp2J92pEic+HArstG+pq6F+H7+/jB9a0Z7576n4Bh4k79/50L1cKMIhZC3MC0iGpl+1IA==", + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.33.tgz", + "integrity": "sha512-VdMCWQOummbhctl4QFMcW6eNtXHsFyDlX60O/tsSQuCcuDOnJ1qPOhhVla65Niece7xq/P2zyZReIO5mP+LGTQ==", "peer": true, "dependencies": { - "@vue/compiler-dom": "3.4.32", - "@vue/compiler-sfc": "3.4.32", - "@vue/runtime-dom": "3.4.32", - "@vue/server-renderer": "3.4.32", - "@vue/shared": "3.4.32" + "@vue/compiler-dom": "3.4.33", + "@vue/compiler-sfc": "3.4.33", + "@vue/runtime-dom": "3.4.33", + "@vue/server-renderer": "3.4.33", + "@vue/shared": "3.4.33" }, "peerDependencies": { "typescript": "*" @@ -8822,9 +9573,9 @@ } }, "node_modules/why-is-node-running": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", - "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "dependencies": { "siginfo": "^2.0.0", @@ -8933,9 +9684,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "engines": { "node": ">=10.0.0" }, diff --git a/src/leapfrogai_ui/package.json b/src/leapfrogai_ui/package.json index b4fd46228..98439ea06 100644 --- a/src/leapfrogai_ui/package.json +++ b/src/leapfrogai_ui/package.json @@ -32,6 +32,7 @@ "@sveltejs/adapter-auto": "^3.2.2", "@sveltejs/adapter-node": "^5.2.0", "@sveltejs/kit": "^2.5.18", + "@tailwindcss/typography": "^0.5.13", "@testing-library/jest-dom": "^6.4.6", "@testing-library/svelte": "^5.2.0", "@testing-library/user-event": "^14.5.2", @@ -41,26 +42,31 @@ "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^7.16.1", "@typescript-eslint/parser": "^7.16.1", - "carbon-components-svelte": "^0.85.0", - "carbon-icons-svelte": "^12.10.0", - "carbon-preprocess-svelte": "^0.11.3", + "autoprefixer": "^10.4.19", "docx": "^8.5.0", "dotenv": "^16.4.5", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.42.0", + "flowbite": "^2.4.1", + "flowbite-svelte": "^0.46.15", + "flowbite-svelte-icons": "^1.6.1", + "husky": "^9.0.11", "jsdom": "^24.1.0", "lodash": "^4.17.21", "msw": "^2.3.1", "otpauth": "^9.3.1", "pdf-lib": "^1.17.1", "playwright": "^1.45.2", + "postcss": "^8.4.38", "pptxgenjs": "^3.12.0", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.6", + "prettier-plugin-tailwindcss": "^0.6.4", "sass": "^1.77.8", "svelte": "^4.2.18", "svelte-check": "^3.8.4", + "tailwindcss": "^3.4.4", "tslib": "^2.6.3", "typescript": "^5.5.3", "vite": "^5.3.4", @@ -71,12 +77,6 @@ "dependencies": { "@ai-sdk/openai": "^0.0.36", "@ai-sdk/svelte": "^0.0.17", - "@carbon/colors": "^11.23.0", - "@carbon/layout": "^11.23.0", - "@carbon/themes": "^11.37.0", - "@carbon/type": "^11.28.0", - "@supabase/auth-ui-shared": "^0.1.8", - "@supabase/auth-ui-svelte": "^0.2.9", "@supabase/ssr": "^0.4.0", "@supabase/supabase-js": "^2.44.4", "@sveltejs/vite-plugin-svelte": "^3.1.1", diff --git a/src/leapfrogai_ui/playwright.config.ts b/src/leapfrogai_ui/playwright.config.ts index eb599e436..4d5025365 100644 --- a/src/leapfrogai_ui/playwright.config.ts +++ b/src/leapfrogai_ui/playwright.config.ts @@ -62,6 +62,8 @@ const devConfig: PlaywrightTestConfig = { port: 4173, stderr: 'pipe' }, + testDir: 'tests', + testMatch: /(.+\.)?(test|spec)\.[jt]s/, use: { baseURL: 'http://localhost:4173' } @@ -70,8 +72,11 @@ const devConfig: PlaywrightTestConfig = { // when e2e testing, use the deployed instance const CI_Config: PlaywrightTestConfig = { use: { - baseURL: 'https://ai.uds.dev' - } + baseURL: 'https://ai.uds.dev', + screenshot: 'only-on-failure', + video: 'retain-on-failure' + }, + reporter: [['html', { outputFolder: 'e2e-report' }]] }; // get the environment type from command line. If none, set it to dev diff --git a/src/leapfrogai_ui/postcss.config.js b/src/leapfrogai_ui/postcss.config.js new file mode 100644 index 000000000..ba8073047 --- /dev/null +++ b/src/leapfrogai_ui/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/src/leapfrogai_ui/src/app.css b/src/leapfrogai_ui/src/app.css new file mode 100644 index 000000000..5b910cc7f --- /dev/null +++ b/src/leapfrogai_ui/src/app.css @@ -0,0 +1,91 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --header-height: 3rem; + --message-input-height: 2.5rem; + scrollbar-color: #4b5563 #1f2937; +} + +/*TODO - can we get rid of some of these?*/ +@layer utilities { + .content { + display: flex; + flex-grow: 1; + flex-direction: column; + overflow: auto; + padding-top: 2rem; + height: calc(100vh - var(--header-height)); + } + + .sidebar-height { + height: calc(100vh - var(--header-height)); + } + + .no-scrollbar { + scrollbar-width: none; + } + + .hide { + opacity: 0; + transition: opacity 0.2s; + } + + .lf-content-container { + display: flex; + height: 100%; + flex-direction: column; + justify-content: space-between; + } + + .centered-flexbox { + display: flex; + justify-content: center; + align-items: center; + } + + .remove-btn-style { + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + outline: inherit; + } + + .link { + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 400; + letter-spacing: 0.16px; + text-decoration: none; + color: #78a9ff; + cursor: pointer; + } + + .z-max { + z-index: 9999; + } +} + +@layer components { + .chat-icon { + width: 32px; + height: 52px; + padding: 14px 0.25rem; + color: white; + } + + .pictogram { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: fill 70ms ease; + &:hover { + background-color: gray; + } + } +} diff --git a/src/leapfrogai_ui/src/app.html b/src/leapfrogai_ui/src/app.html index 84ffad166..ee4722804 100644 --- a/src/leapfrogai_ui/src/app.html +++ b/src/leapfrogai_ui/src/app.html @@ -1,5 +1,5 @@ - + diff --git a/src/leapfrogai_ui/src/lib/components/AssistantAvatar.svelte b/src/leapfrogai_ui/src/lib/components/AssistantAvatar.svelte index 613becd56..ceca70148 100644 --- a/src/leapfrogai_ui/src/lib/components/AssistantAvatar.svelte +++ b/src/leapfrogai_ui/src/lib/components/AssistantAvatar.svelte @@ -1,62 +1,93 @@ -
+
- -
- - - - - - - - -
- {#if avatarToShow} -
-
+
+
+
+ (selectedRadioButton = 'pictogram')} + class="dark:text-gray-400">Pictogram + (selectedRadioButton = 'upload')} + class="dark:text-gray-400">Upload +
+
+
- {/if} - -
-
Upload image
-
Supported file types are .jpg and .png.
- +
+
+
+ 0 ? filteredPictograms : pictogramNames} + bind:selectedPictogramName={tempPictogram} /> -
- {#if hideUploader} -
- + {#if avatarToShow} + + {/if} + +
+

Upload image

+

Supported file types are .jpg and .png.

+ -
- {/if} + Upload from computer + - {#if shouldValidate && (fileNotUploaded || fileTooBig)} -
-
{errorMsg}
+
- {/if} -
-
-
- - + +
+ + + { + const file = e.currentTarget.files[0]; + $form.avatarFile = file ?? null; + }} + multiple={false} + type="file" + tabindex="-1" + accept={['.jpg', '.jpeg', '.png']} + name="avatarFile" + class="sr-only" + /> + +
diff --git a/src/leapfrogai_ui/src/lib/components/AssistantCard.svelte b/src/leapfrogai_ui/src/lib/components/AssistantCard.svelte new file mode 100644 index 000000000..36cc4fd96 --- /dev/null +++ b/src/leapfrogai_ui/src/lib/components/AssistantCard.svelte @@ -0,0 +1,101 @@ + + +
+ +
+ + + goto(`/chat/assistants-management/edit/${assistant.id}`)} + >Edit + (deleteModalOpen = true)}>Delete + +
+ +
+ {#if assistant.metadata.avatar} + + {:else} + + {/if} + +
+ {assistant.name && assistant.name.length > 20 + ? `${assistant.name.slice(0, 20)}...` + : assistant.name} +
+ + {assistant.description && assistant.description.length > 75 + ? `${assistant.description?.slice(0, 75)}...` + : assistant.description} +
+
+
+ +
+ +

+ Are you sure you want to delete your + {assistant.name} + assistant? +

+
+ + +
+
diff --git a/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte b/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte new file mode 100644 index 000000000..3c903f0a9 --- /dev/null +++ b/src/leapfrogai_ui/src/lib/components/AssistantFileDropdown.svelte @@ -0,0 +1,96 @@ + + +
+ + +
+
+ { + const fileList = e.detail; + filesStore.setUploading(true); + filesStore.addUploadingFiles(fileList, { autoSelectUploadedFiles: true }); + submit(); + }}>Upload new data source +
+
+
+ {#each $filesStore.files?.map( (file) => ({ id: file.id, text: file.filename }) ) as file (file.id)} +
  • + handleClick(file.id)} + checked={$filesStore.selectedAssistantFileIds.includes(file.id)} + class="overflow-hidden text-ellipsis whitespace-nowrap">{file.text} +
  • + {/each} +
    +
    +
    diff --git a/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.svelte b/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.svelte index b3b065e2b..868593e3f 100644 --- a/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.svelte +++ b/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.svelte @@ -1,10 +1,10 @@ -
    - ({ id: file.id, text: file.filename }))} - direction="top" - accept={ACCEPTED_FILE_TYPES} - bind:selectedIds={$filesStore.selectedAssistantFileIds} - {filesForm} - /> -
    + -
    +
    {#each filteredStoreFiles as file} -
    +
    { filesStore.setSelectedAssistantFileIds( $filesStore.selectedAssistantFileIds.filter((id) => id !== file.id) @@ -50,25 +39,3 @@
    - - diff --git a/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.test.ts b/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.test.ts index 5c938b175..6bb15f2ae 100644 --- a/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.test.ts +++ b/src/leapfrogai_ui/src/lib/components/AssistantFileSelect.test.ts @@ -56,6 +56,7 @@ describe('AssistantFileSelect', () => { screen.queryByTestId(`${mockFiles[0].filename}-${mockFiles[0].status}-uploader-item`) ).not.toBeInTheDocument(); + await userEvent.click(screen.getByTestId('file-select-dropdown-btn')); await userEvent.click(screen.getByText(mockFiles[0].filename)); screen.getByTestId(`${mockFiles[0].filename}-${mockFiles[0].status}-uploader-item`); }); diff --git a/src/leapfrogai_ui/src/lib/components/AssistantForm.svelte b/src/leapfrogai_ui/src/lib/components/AssistantForm.svelte index b96c19dcb..2b738982c 100644 --- a/src/leapfrogai_ui/src/lib/components/AssistantForm.svelte +++ b/src/leapfrogai_ui/src/lib/components/AssistantForm.svelte @@ -7,15 +7,17 @@ import { superForm } from 'sveltekit-superforms'; import { page } from '$app/stores'; import { beforeNavigate, goto } from '$app/navigation'; - import { Button, Modal, Slider, TextArea, TextInput } from 'carbon-components-svelte'; - import AssistantAvatar from '$components/AssistantAvatar.svelte'; + import { Button, Modal, P } from 'flowbite-svelte'; + import Slider from '$components/Slider.svelte'; import { yup } from 'sveltekit-superforms/adapters'; import { filesStore, toastStore } from '$stores'; - import InputTooltip from '$components/InputTooltip.svelte'; import { assistantInputSchema, editAssistantInputSchema } from '$lib/schemas/assistants'; import type { NavigationTarget } from '@sveltejs/kit'; import { onMount } from 'svelte'; import AssistantFileSelect from '$components/AssistantFileSelect.svelte'; + import LFInput from '$components/LFInput.svelte'; + import LFLabel from '$components/LFLabel.svelte'; + import AssistantAvatar from '$components/AssistantAvatar.svelte'; export let data; @@ -53,8 +55,6 @@ }); let cancelModalOpen = false; - let files: File[] = []; - let selectedPictogramName = isEditMode ? $form.pictogram : 'default'; let navigateTo: NavigationTarget; let leavePageConfirmed = false; @@ -86,171 +86,110 @@ }); -
    -
    -
    -
    -
    {`${isEditMode ? 'Edit' : 'New'} Assistant`}
    - -
    - - - - - - - - - -