diff --git a/.devcontainer/buildAppImage.sh b/.devcontainer/buildAppImage.sh new file mode 100644 index 000000000..efb963d5a --- /dev/null +++ b/.devcontainer/buildAppImage.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +make clean + +# To reproduce https://github.com/menloresearch/jan/pull/5463 +TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri" +mkdir -p "$TAURI_TOOLKIT_PATH" +wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage" +chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage" + +jq '.bundle.resources = ["resources/pre-install/**/*"] | .bundle.externalBin = ["binaries/cortex-server", "resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json +mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + +make build-tauri + +cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun +mkdir -p ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/engines +cp -f ./src-tauri/binaries/deps/*.so* ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/ +cp -f ./src-tauri/binaries/*.so* ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/ +cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/ +APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1) +echo $APP_IMAGE +rm -f $APP_IMAGE +/opt/bin/appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md new file mode 100644 index 000000000..495adb305 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-bug-report.md @@ -0,0 +1,24 @@ +--- +name: 🐛 Bug Report +about: If something isn't working as expected 🤔 +title: 'bug: ' +type: Bug +--- + +**Version:** e.g. 0.5.x-xxx + +## Describe the Bug + + + +## Steps to Reproduce +1. + +## Screenshots / Logs + + + +## Operating System +- [ ] MacOS +- [ ] Windows +- [ ] Linux diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md new file mode 100644 index 000000000..4da9e8450 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2-feature-request.md @@ -0,0 +1,12 @@ +--- +name: 🚀 Feature Request +about: Suggest an idea for this project 😻! +title: 'idea: ' +type: Idea +--- + +## Problem Statement + + +## Feature Idea + diff --git a/.github/ISSUE_TEMPLATE/3-epic.md b/.github/ISSUE_TEMPLATE/3-epic.md new file mode 100644 index 000000000..afffc6b5c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/3-epic.md @@ -0,0 +1,12 @@ +--- +name: 🌟 Epic +about: Major building block that advances Jan's goals +title: 'epic: ' +type: Epic +--- + +## Goal + +## Tasklist + +## Out of scope diff --git a/.github/ISSUE_TEMPLATE/4-goal.md b/.github/ISSUE_TEMPLATE/4-goal.md new file mode 100644 index 000000000..28b32382a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/4-goal.md @@ -0,0 +1,13 @@ +--- +name: 🎯 Goal +about: External communication of Jan's roadmap and objectives +title: 'goal: ' +type: Goal +--- + +## Goal + +## Tasklist + +## Out of scope + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 940383f72..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: "\U0001F41B Bug Report" -description: "If something isn't working as expected \U0001F914" -title: 'bug: [DESCRIPTION]' - -body: - - type: input - validations: - required: true - attributes: - label: "Jan version" - description: "**Tip:** The version is in the app's bottom right corner" - placeholder: "e.g. 0.5.x-xxx" - - - type: textarea - validations: - required: true - attributes: - label: "Describe the Bug" - description: "A clear & concise description of the bug" - - - type: textarea - attributes: - label: "Steps to Reproduce" - description: | - Please list out steps to reproduce the issue - placeholder: | - 1. Go to '...' - 2. Click on '...' - - - type: textarea - attributes: - label: "Screenshots / Logs" - description: | - You can find logs in: ~/jan/logs/app.logs - - - type: checkboxes - attributes: - label: "What is your OS?" - options: - - label: MacOS - - label: Windows - - label: Linux diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 2d49f0d6e..057cb8148 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,5 @@ -## To encourage contributors to use issue templates, we don't allow blank issues blank_issues_enabled: true - contact_links: - - name: "\1F4AC Jan Discussions" - url: "https://github.com/orgs/menloresearch/discussions/categories/q-a" - about: "Get help, discuss features & roadmap, and share your projects" \ No newline at end of file + - name: Jan Discussions + url: https://github.com/orgs/menloresearch/discussions/categories/q-a + about: Get help, discuss features & roadmap, and share your projects diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 8e7fb4ca7..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "\U0001F680 Feature Request" -description: "Suggest an idea for this project \U0001F63B!" -title: 'idea: [DESCRIPTION]' -labels: 'feature request' -body: - - type: textarea - validations: - required: true - attributes: - label: "Problem Statement" - description: "Describe the problem you're facing" - placeholder: | - I'm always frustrated when ... - - - type: textarea - validations: - required: true - attributes: - label: "Feature Idea" - description: "Describe what you want instead. Examples are welcome!" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/model_request.yml b/.github/ISSUE_TEMPLATE/model_request.yml deleted file mode 100644 index 7f7c4f63c..000000000 --- a/.github/ISSUE_TEMPLATE/model_request.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: "\U0001F929 Model Request" -description: "Request a new model to be compiled" -title: 'feat: [DESCRIPTION]' -labels: 'type: model request' -body: - - type: markdown - attributes: - value: "**Tip:** Download any HuggingFace model in app ([see guides](https://jan.ai/docs/models/manage-models#add-models)). Use this form for unsupported models only." - - type: textarea - validations: - required: true - attributes: - label: "Model Requests" - description: "If applicable, include the source URL, licenses, and any other relevant information" - - type: checkboxes - attributes: - label: "Which formats?" - options: - - label: GGUF (llama.cpp) - - label: TensorRT (TensorRT-LLM) - - label: ONNX (Onnx Runtime) diff --git a/.github/ISSUE_TEMPLATE/roadmap.md b/.github/ISSUE_TEMPLATE/roadmap.md deleted file mode 100644 index 7947f31bf..000000000 --- a/.github/ISSUE_TEMPLATE/roadmap.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Roadmap -about: Plan Roadmap items with subtasks -title: 'roadmap: ' -labels: 'type: planning' -assignees: '' - ---- - -## Goal - -## Tasklist - -### Frontend -- [ ] link to janhq/jan epics - -**Bugs** -- [ ] link to bugs - -### Backend -- [ ] link to janhq/cortex.cpp epics - -**Bugs** -- [ ] link to bug issues - -### Infra -- [ ] link to infra issues - -### Administrative / Management -- [ ] link to infra issues - -### Marketing - -------- -## Resources diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml deleted file mode 100644 index b4d275658..000000000 --- a/.github/workflows/jan-electron-build-nightly.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: Electron Builder - Nightly / Manual - -on: - schedule: - - cron: '0 20 * * 1,2,3' # At 8 PM UTC on Monday, Tuesday, and Wednesday which is 3 AM UTC+7 Tuesday, Wednesday, and Thursday - workflow_dispatch: - inputs: - public_provider: - type: choice - description: 'Public Provider' - options: - - none - - aws-s3 - default: none - pull_request: - branches: - - release/** - -jobs: - set-public-provider: - runs-on: ubuntu-latest - outputs: - public_provider: ${{ steps.set-public-provider.outputs.public_provider }} - ref: ${{ steps.set-public-provider.outputs.ref }} - steps: - - name: Set public provider - id: set-public-provider - run: | - if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - echo "::set-output name=public_provider::${{ github.event.inputs.public_provider }}" - echo "::set-output name=ref::${{ github.ref }}" - else - if [ "${{ github.event_name }}" == "schedule" ]; then - echo "::set-output name=public_provider::aws-s3" - echo "::set-output name=ref::refs/heads/dev" - elif [ "${{ github.event_name }}" == "push" ]; then - echo "::set-output name=public_provider::aws-s3" - echo "::set-output name=ref::${{ github.ref }}" - elif [ "${{ github.event_name }}" == "pull_request_review" ]; then - echo "::set-output name=public_provider::none" - echo "::set-output name=ref::${{ github.ref }}" - else - echo "::set-output name=public_provider::none" - echo "::set-output name=ref::${{ github.ref }}" - fi - fi - # Job create Update app version based on latest release tag with build number and save to output - get-update-version: - uses: ./.github/workflows/template-get-update-version.yml - - build-tauri-macos: - uses: ./.github/workflows/template-tauri-build-macos.yml - secrets: inherit - needs: [get-update-version, set-public-provider] - with: - ref: ${{ needs.set-public-provider.outputs.ref }} - public_provider: ${{ needs.set-public-provider.outputs.public_provider }} - new_version: ${{ needs.get-update-version.outputs.new_version }} - channel: nightly - cortex_api_port: "39261" - - build-tauri-windows-x64: - uses: ./.github/workflows/template-tauri-build-windows-x64.yml - secrets: inherit - needs: [get-update-version, set-public-provider] - with: - ref: ${{ needs.set-public-provider.outputs.ref }} - public_provider: ${{ needs.set-public-provider.outputs.public_provider }} - new_version: ${{ needs.get-update-version.outputs.new_version }} - channel: nightly - cortex_api_port: "39261" - - build-tauri-linux-x64: - uses: ./.github/workflows/template-tauri-build-linux-x64.yml - secrets: inherit - needs: [get-update-version, set-public-provider] - with: - ref: ${{ needs.set-public-provider.outputs.ref }} - public_provider: ${{ needs.set-public-provider.outputs.public_provider }} - new_version: ${{ needs.get-update-version.outputs.new_version }} - channel: nightly - cortex_api_port: "39261" - - sync-temp-to-latest: - needs: [get-update-version, set-public-provider, build-tauri-windows-x64, build-tauri-linux-x64, build-tauri-macos] - runs-on: ubuntu-latest - steps: - - name: Getting the repo - uses: actions/checkout@v3 - - name: Install jq - uses: dcarbone/install-jq-action@v2.0.1 - - name: create latest.json file - run: | - VERSION=${{ needs.get-update-version.outputs.new_version }} - PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") - LINUX_SIGNATURE="${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_SIG }}" - LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_FILE_NAME }}" - WINDOWS_SIGNATURE="${{ needs.build-tauri-windows-x64.outputs.WIN_SIG }}" - WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-windows-x64.outputs.FILE_NAME }}" - DARWIN_SIGNATURE="${{ needs.build-tauri-macos.outputs.MAC_UNIVERSAL_SIG }}" - DARWIN_URL="https://delta.jan.ai/nightly/Jan-nightly_${{ needs.get-update-version.outputs.new_version }}.app.tar.gz" - - jq --arg version "$VERSION" \ - --arg pub_date "$PUB_DATE" \ - --arg linux_signature "$LINUX_SIGNATURE" \ - --arg linux_url "$LINUX_URL" \ - --arg windows_signature "$WINDOWS_SIGNATURE" \ - --arg windows_url "$WINDOWS_URL" \ - --arg darwin_arm_signature "$DARWIN_SIGNATURE" \ - --arg darwin_arm_url "$DARWIN_URL" \ - --arg darwin_amd_signature "$DARWIN_SIGNATURE" \ - --arg darwin_amd_url "$DARWIN_URL" \ - '.version = $version - | .pub_date = $pub_date - | .platforms["linux-x86_64"].signature = $linux_signature - | .platforms["linux-x86_64"].url = $linux_url - | .platforms["windows-x86_64"].signature = $windows_signature - | .platforms["windows-x86_64"].url = $windows_url - | .platforms["darwin-aarch64"].signature = $darwin_arm_signature - | .platforms["darwin-aarch64"].url = $darwin_arm_url - | .platforms["darwin-x86_64"].signature = $darwin_amd_signature - | .platforms["darwin-x86_64"].url = $darwin_amd_url' \ - src-tauri/latest.json.template > latest.json - cat latest.json - - name: Sync temp to latest - if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }} - run: | - aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/latest.json - aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/nightly/ - env: - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }} - AWS_EC2_METADATA_DISABLED: "true" - - noti-discord-nightly-and-update-url-readme: - needs: [ - build-tauri-macos, - build-tauri-windows-x64, - build-tauri-linux-x64, - get-update-version, - set-public-provider, - sync-temp-to-latest - ] - secrets: inherit - if: github.event_name == 'schedule' - uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml - with: - ref: refs/heads/dev - build_reason: Nightly - push_to_branch: dev - new_version: ${{ needs.get-update-version.outputs.new_version }} - - noti-discord-pre-release-and-update-url-readme: - needs: [ - build-tauri-macos, - build-tauri-windows-x64, - build-tauri-linux-x64, - get-update-version, - set-public-provider, - sync-temp-to-latest - ] - secrets: inherit - if: github.event_name == 'push' - uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml - with: - ref: refs/heads/dev - build_reason: Pre-release - push_to_branch: dev - new_version: ${{ needs.get-update-version.outputs.new_version }} - - noti-discord-manual-and-update-url-readme: - needs: [ - build-tauri-macos, - build-tauri-windows-x64, - build-tauri-linux-x64, - get-update-version, - set-public-provider, - sync-temp-to-latest - ] - secrets: inherit - if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3' - uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml - with: - ref: refs/heads/dev - build_reason: Manual - push_to_branch: dev - new_version: ${{ needs.get-update-version.outputs.new_version }} - - - # comment-pr-build-url: - # needs: [ - # build-tauri-macos, - # build-tauri-windows-x64, - # build-tauri-linux-x64, - # get-update-version, - # set-public-provider, - # sync-temp-to-latest - # ] - # runs-on: ubuntu-latest - # if: github.event_name == 'pull_request_review' - # steps: - # - name: Set up GitHub CLI - # run: | - # curl -sSL https://github.com/cli/cli/releases/download/v2.33.0/gh_2.33.0_linux_amd64.tar.gz | tar xz - # sudo cp gh_2.33.0_linux_amd64/bin/gh /usr/local/bin/ - - # - name: Comment build URL on PR - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # run: | - # PR_URL=${{ github.event.pull_request.html_url }} - # RUN_ID=${{ github.run_id }} - # COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})." - # gh pr comment $PR_URL --body "$COMMENT" \ No newline at end of file diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml deleted file mode 100644 index a223027f3..000000000 --- a/.github/workflows/jan-electron-build.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Electron Builder - Tag - -on: - push: - tags: ["v[0-9]+.[0-9]+.[0-9]+"] - -jobs: - # Job create Update app version based on latest release tag with build number and save to output - get-update-version: - uses: ./.github/workflows/template-get-update-version.yml - - create-draft-release: - runs-on: ubuntu-latest - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') - outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} - version: ${{ steps.get_version.outputs.version }} - permissions: - contents: write - steps: - - name: Extract tag name without v prefix - id: get_version - run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}" - env: - GITHUB_REF: ${{ github.ref }} - - name: Create Draft Release - id: create_release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ github.ref_name }} - token: ${{ secrets.GITHUB_TOKEN }} - name: "${{ env.VERSION }}" - draft: true - prerelease: false - - build-electron-macos: - uses: ./.github/workflows/template-electron-build-macos.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - beta: false - nightly: false - new_version: ${{ needs.get-update-version.outputs.new_version }} - - build-electron-windows-x64: - uses: ./.github/workflows/template-electron-build-windows-x64.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - beta: false - nightly: false - new_version: ${{ needs.get-update-version.outputs.new_version }} - - build-electron-linux-x64: - uses: ./.github/workflows/template-electron-build-linux-x64.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - beta: false - nightly: false - new_version: ${{ needs.get-update-version.outputs.new_version }} - - # build-tauri-macos: - # uses: ./.github/workflows/template-tauri-build-macos.yml - # secrets: inherit - # needs: [get-update-version, create-draft-release] - # with: - # ref: ${{ github.ref }} - # public_provider: github - # channel: stable - # new_version: ${{ needs.get-update-version.outputs.new_version }} - # upload_url: ${{ needs.create-draft-release.outputs.upload_url }} - - # build-tauri-windows-x64: - # uses: ./.github/workflows/template-tauri-build-windows-x64.yml - # secrets: inherit - # needs: [get-update-version, create-draft-release] - # with: - # ref: ${{ github.ref }} - # public_provider: github - # channel: stable - # new_version: ${{ needs.get-update-version.outputs.new_version }} - # upload_url: ${{ needs.create-draft-release.outputs.upload_url }} - - # build-tauri-linux-x64: - # uses: ./.github/workflows/template-tauri-build-linux-x64.yml - # secrets: inherit - # needs: [get-update-version, create-draft-release] - # with: - # ref: ${{ github.ref }} - # public_provider: github - # channel: stable - # new_version: ${{ needs.get-update-version.outputs.new_version }} - # upload_url: ${{ needs.create-draft-release.outputs.upload_url }} - - update_release_draft: - needs: [ - build-electron-windows-x64, - build-electron-linux-x64, - build-electron-macos, - build-tauri-windows-x64, - build-tauri-linux-x64, - build-tauri-macos - ] - permissions: - # write permission is required to create a github release - contents: write - # write permission is required for autolabeler - # otherwise, read permission is required at least - pull-requests: write - runs-on: ubuntu-latest - steps: - # (Optional) GitHub Enterprise requires GHE_HOST variable set - #- name: Set GHE_HOST - # run: | - # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV - - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: release-drafter/release-drafter@v5 - # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml - # with: - # config-name: my-config.yml - # disable-autolabeler: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-linter-and-test.yml similarity index 50% rename from .github/workflows/jan-electron-linter-and-test.yml rename to .github/workflows/jan-linter-and-test.yml index 6c2842c30..ae1f81f61 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-linter-and-test.yml @@ -6,8 +6,7 @@ on: - main - dev paths: - - 'electron/**' - - .github/workflows/jan-electron-linter-and-test.yml + - .github/workflows/jan-linter-and-test.yml - 'web/**' - 'joi/**' - 'package.json' @@ -24,8 +23,7 @@ on: - dev - release/** paths: - - 'electron/**' - - .github/workflows/jan-electron-linter-and-test.yml + - .github/workflows/jan-linter-and-test.yml - 'web/**' - 'joi/**' - 'package.json' @@ -67,8 +65,8 @@ jobs: path: ./coverage/lcov.info test-on-macos: - if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch' - runs-on: macos-latest + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'macos-latest' || 'macos-selfhosted-12-arm64' }} + if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' steps: - name: Getting the repo uses: actions/checkout@v3 @@ -81,48 +79,8 @@ jobs: node-version: 20 - name: Set IS_TEST environment variable - run: | - echo "IS_TEST=true" >> $GITHUB_ENV - - - name: 'Cleanup cache' - continue-on-error: true - run: | - rm -rf ~/jan - make clean - - - name: Get Commit Message for PR - if: github.event_name == 'pull_request' - run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV - - - name: Get Commit Message for push event - if: github.event_name == 'push' - run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV - - # - name: 'Config report portal' - # run: | - # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}" - - - name: Linter and test - run: | - make test - env: - CSC_IDENTITY_AUTO_DISCOVERY: 'false' - - test-on-macos-pr-target: - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - runs-on: macos-latest - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Installing node - uses: actions/setup-node@v3 - with: - node-version: 20 + if: github.event.pull_request.head.repo.full_name == github.repository + run: echo "IS_TEST=true" >> $GITHUB_ENV - name: 'Cleanup cache' continue-on-error: true @@ -154,6 +112,10 @@ jobs: with: node-version: 20 + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + # Clean cache, continue on error - name: 'Cleanup cache' shell: powershell @@ -167,37 +129,33 @@ jobs: } make clean - - name: Get Commit Message for push event - if: github.event_name == 'push' - shell: bash - run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV - - # - name: 'Config report portal' - # shell: bash - # run: | - # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}" - - name: Linter and test shell: powershell run: | make test test-on-windows-pr: - if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'workflow_dispatch' - runs-on: windows-latest + if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'windows-latest' || 'WINDOWS-11' }} steps: - name: Getting the repo uses: actions/checkout@v3 with: fetch-depth: 0 + - name: install dependencies + run: | + choco install --yes --no-progress make + - name: Installing node uses: actions/setup-node@v3 with: node-version: 20 - # Clean cache, continue on error + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + - name: 'Cleanup cache' shell: powershell continue-on-error: true @@ -210,57 +168,22 @@ jobs: } make clean - - name: Get Commit Message for PR - if: github.event_name == 'pull_request' - shell: bash + - name: Install WebView2 Runtime (Bootstrapper) + shell: powershell run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV - - # - name: 'Config report portal' - # shell: bash - # run: | - # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}" - - - name: Linter and test - shell: powershell - run: | - make test - - test-on-windows-pr-target: - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - runs-on: windows-latest - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Installing node - uses: actions/setup-node@v1 - with: - node-version: 20 - - # Clean cache, continue on error - - name: 'Cleanup cache' - shell: powershell - continue-on-error: true - run: | - $path = "$Env:APPDATA\jan" - if (Test-Path $path) { - Remove-Item "\\?\$path" -Recurse -Force - } else { - Write-Output "Folder does not exist." - } - make clean + Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe' + Start-Process -FilePath setup.exe -Verb RunAs -Wait - name: Linter and test shell: powershell run: | make test + env: + NODE_OPTIONS: '--max-old-space-size=2048' test-on-ubuntu: - runs-on: ubuntu-latest - if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch' + runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'ubuntu-latest' || 'ubuntu-latest' }} + if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch' steps: - name: Getting the repo uses: actions/checkout@v3 @@ -272,27 +195,21 @@ jobs: with: node-version: 20 + - name: Install Tauri dependencies + run: | + sudo apt update + sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver + + - name: Install tauri-driver dependencies + run: | + cargo install tauri-driver --locked + - name: 'Cleanup cache' continue-on-error: true run: | rm -rf ~/jan make clean - - name: Get Commit Message for PR - if: github.event_name == 'pull_request' - run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV - - - name: Get Commit Message for push event - if: github.event_name == 'push' - run: | - echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV - - # - name: 'Config report portal' - # shell: bash - # run: | - # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}" - - name: Linter and test run: | export DISPLAY=$(w -h | awk 'NR==1 {print $2}') @@ -353,29 +270,3 @@ jobs: # base-lcov-file: './lcov.info' # send-summary-comment: true # show-annotations: 'warning' - - test-on-ubuntu-pr-target: - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Installing node - uses: actions/setup-node@v3 - with: - node-version: 20 - - - name: 'Cleanup cache' - continue-on-error: true - run: | - rm -rf ~/jan - make clean - - - name: Linter and test - run: | - export DISPLAY=$(w -h | awk 'NR==1 {print $2}') - echo -e "Display ID: $DISPLAY" - make test diff --git a/.github/workflows/publish-npm-core.yml b/.github/workflows/publish-npm-core.yml index 403deb100..f719ab3d4 100644 --- a/.github/workflows/publish-npm-core.yml +++ b/.github/workflows/publish-npm-core.yml @@ -6,7 +6,6 @@ on: workflow_dispatch: jobs: build-and-publish-plugins: - environment: production runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/template-electron-build-linux-x64.yml b/.github/workflows/template-electron-build-linux-x64.yml deleted file mode 100644 index 4dfab5c09..000000000 --- a/.github/workflows/template-electron-build-linux-x64.yml +++ /dev/null @@ -1,188 +0,0 @@ -name: build-linux-x64 -on: - workflow_call: - inputs: - ref: - required: true - type: string - default: 'refs/heads/main' - public_provider: - required: true - type: string - default: none - description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' - new_version: - required: true - type: string - default: '' - aws_s3_prefix: - required: false - type: string - default: '/latest/' - beta: - required: false - type: boolean - default: false - nightly: - required: false - type: boolean - default: false - cortex_api_port: - required: false - type: string - default: null - secrets: - DELTA_AWS_S3_BUCKET_NAME: - required: false - DELTA_AWS_ACCESS_KEY_ID: - required: false - DELTA_AWS_SECRET_ACCESS_KEY: - required: false - -jobs: - build-linux-x64: - if: inputs.public_provider == 'github' || inputs.public_provider == 'none' - runs-on: ubuntu-latest - environment: production - permissions: - contents: write - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Replace Icons for Beta Build - if: inputs.beta == true && inputs.nightly != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico - cp electron/icons_dev/jan-beta.png electron/icons/icon.png - cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png - - - name: Replace Icons for Nightly Build - if: inputs.nightly == true && inputs.beta != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico - cp electron/icons_dev/jan-nightly.png electron/icons/icon.png - cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png - - - - name: Installing node - uses: actions/setup-node@v1 - with: - node-version: 20 - - - name: Install jq - uses: dcarbone/install-jq-action@v2.0.1 - - - name: Update app version base public_provider - if: inputs.public_provider != 'github' - run: | - echo "Version: ${{ inputs.new_version }}" - # Update the version in electron/package.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json nightly - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json nightly - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - - - name: Change App Name for beta version - if: inputs.beta == true - shell: bash - run: | - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json beta - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json beta - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - cat ./package.json - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - - - name: Update app version base on tag - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' - run: | - jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - env: - VERSION_TAG: ${{ inputs.new_version }} - - - name: Build and publish app to aws s3 r2 or github artifactory - if: inputs.public_provider != 'github' - run: | - # check public_provider is true or not - echo "public_provider is ${{ inputs.public_provider }}" - if [ "${{ inputs.public_provider }}" == "none" ]; then - make build - else - make build-and-publish - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - CORTEX_API_PORT: ${{ inputs.cortex_api_port }} - - - name: Build and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Build and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Upload Artifact .deb file - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-electron-linux-amd64-${{ inputs.new_version }}-deb - path: ./electron/dist/*.deb - - - name: Upload Artifact .AppImage file - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-electron-linux-amd64-${{ inputs.new_version }}-AppImage - path: ./electron/dist/*.AppImage \ No newline at end of file diff --git a/.github/workflows/template-electron-build-macos.yml b/.github/workflows/template-electron-build-macos.yml deleted file mode 100644 index ab9f002cb..000000000 --- a/.github/workflows/template-electron-build-macos.yml +++ /dev/null @@ -1,234 +0,0 @@ -name: build-macos -on: - workflow_call: - inputs: - ref: - required: true - type: string - default: 'refs/heads/main' - public_provider: - required: true - type: string - default: none - description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' - new_version: - required: true - type: string - default: '' - aws_s3_prefix: - required: false - type: string - default: '/latest/' - beta: - required: false - type: boolean - default: false - nightly: - required: false - type: boolean - default: false - cortex_api_port: - required: false - type: string - default: null - secrets: - DELTA_AWS_S3_BUCKET_NAME: - required: false - DELTA_AWS_ACCESS_KEY_ID: - required: false - DELTA_AWS_SECRET_ACCESS_KEY: - required: false - CODE_SIGN_P12_BASE64: - required: false - CODE_SIGN_P12_PASSWORD: - required: false - APPLE_ID: - required: false - APPLE_APP_SPECIFIC_PASSWORD: - required: false - DEVELOPER_ID: - required: false - -jobs: - build-macos: - if: inputs.public_provider == 'github' || inputs.public_provider == 'none' - runs-on: macos-latest - environment: production - permissions: - contents: write - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Replace Icons for Beta Build - if: inputs.beta == true && inputs.nightly != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico - cp electron/icons_dev/jan-beta.png electron/icons/icon.png - cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png - - - name: Replace Icons for Nightly Build - if: inputs.nightly == true && inputs.beta != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico - cp electron/icons_dev/jan-nightly.png electron/icons/icon.png - cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png - - - name: Installing node - uses: actions/setup-node@v1 - with: - node-version: 20 - - - name: Install jq - uses: dcarbone/install-jq-action@v2.0.1 - - - name: Update app version based on latest release tag with build number - if: inputs.public_provider != 'github' - run: | - echo "Version: ${{ inputs.new_version }}" - # Update the version in electron/package.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - - jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - - # cat electron/package.json - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json nightly - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json nightly - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - - - name: Change App Name for beta version - if: inputs.beta == true - shell: bash - run: | - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json beta - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json beta - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - cat ./package.json - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - - - name: Update app version base on tag - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' - run: | - jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - env: - VERSION_TAG: ${{ inputs.new_version }} - - - name: Get Cer for code signing - run: base64 -d <<< "$CODE_SIGN_P12_BASE64" > /tmp/codesign.p12 - shell: bash - env: - CODE_SIGN_P12_BASE64: ${{ secrets.CODE_SIGN_P12_BASE64 }} - - - uses: apple-actions/import-codesign-certs@v2 - continue-on-error: true - with: - p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }} - p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }} - - - name: Build and publish app to aws s3 r2 or github artifactory - if: inputs.public_provider != 'github' - run: | - # check public_provider is true or not - echo "public_provider is ${{ inputs.public_provider }}" - if [ "${{ inputs.public_provider }}" == "none" ]; then - make build - else - make build-and-publish - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CSC_LINK: '/tmp/codesign.p12' - CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} - CSC_IDENTITY_AUTO_DISCOVERY: 'true' - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APP_PATH: '.' - DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }} - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: auto - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - CORTEX_API_PORT: ${{ inputs.cortex_api_port }} - - - name: Build and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CSC_LINK: '/tmp/codesign.p12' - CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} - CSC_IDENTITY_AUTO_DISCOVERY: 'true' - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APP_PATH: '.' - DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }} - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Build and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CSC_LINK: '/tmp/codesign.p12' - CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} - CSC_IDENTITY_AUTO_DISCOVERY: 'true' - APPLE_ID: ${{ secrets.APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - APP_PATH: '.' - DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }} - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: auto - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Upload Artifact - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-electron-mac-universal-${{ inputs.new_version }} - path: ./electron/dist/*.dmg \ No newline at end of file diff --git a/.github/workflows/template-electron-build-windows-x64.yml b/.github/workflows/template-electron-build-windows-x64.yml deleted file mode 100644 index 9f71dadb0..000000000 --- a/.github/workflows/template-electron-build-windows-x64.yml +++ /dev/null @@ -1,230 +0,0 @@ -name: build-windows-x64 -on: - workflow_call: - inputs: - ref: - required: true - type: string - default: 'refs/heads/main' - public_provider: - required: true - type: string - default: none - description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3' - new_version: - required: true - type: string - default: '' - aws_s3_prefix: - required: false - type: string - default: '/latest/' - beta: - required: false - type: boolean - default: false - nightly: - required: false - type: boolean - default: false - cortex_api_port: - required: false - type: string - default: null - secrets: - DELTA_AWS_S3_BUCKET_NAME: - required: false - DELTA_AWS_ACCESS_KEY_ID: - required: false - DELTA_AWS_SECRET_ACCESS_KEY: - required: false - AZURE_KEY_VAULT_URI: - required: false - AZURE_CLIENT_ID: - required: false - AZURE_TENANT_ID: - required: false - AZURE_CLIENT_SECRET: - required: false - AZURE_CERT_NAME: - required: false - -jobs: - build-windows-x64: - if: inputs.public_provider == 'github' || inputs.public_provider == 'none' - runs-on: windows-latest - permissions: - contents: write - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Replace Icons for Beta Build - if: inputs.beta == true && inputs.nightly != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico - cp electron/icons_dev/jan-beta.png electron/icons/icon.png - cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png - - - name: Replace Icons for Nightly Build - if: inputs.nightly == true && inputs.beta != true - shell: bash - run: | - rm -rf electron/icons/* - - cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png - cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico - cp electron/icons_dev/jan-nightly.png electron/icons/icon.png - cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png - cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png - - - name: Installing node - uses: actions/setup-node@v1 - with: - node-version: 20 - - - name: Install jq - uses: dcarbone/install-jq-action@v2.0.1 - - - name: Update app version base on tag - if: inputs.public_provider != 'github' - id: version_update - shell: bash - run: | - echo "Version: ${{ inputs.new_version }}" - # Update the version in electron/package.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - - jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json nightly - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json nightly - chmod +x .github/scripts/rename-uninstaller.sh - .github/scripts/rename-uninstaller.sh nightly - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - cat ./package.json - echo "------------------------" - - - name: Change App Name for beta version - if: inputs.beta == true - shell: bash - run: | - chmod +x .github/scripts/rename-app.sh - .github/scripts/rename-app.sh ./electron/package.json beta - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json beta - chmod +x .github/scripts/rename-uninstaller.sh - .github/scripts/rename-uninstaller.sh beta - echo "------------------------" - cat ./electron/package.json - echo "------------------------" - cat ./package.json - echo "------------------------" - cat ./electron/scripts/uninstaller.nsh - jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - cat electron/package.json - - - name: Update app version base on tag - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' - shell: bash - run: | - jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json - env: - VERSION_TAG: ${{ inputs.new_version }} - - - name: Install AzureSignTool - run: | - dotnet tool install --global AzureSignTool - - - name: Build and publish app to aws s3 r2 or github artifactory - shell: bash - if: inputs.public_provider != 'github' - run: | - # check public_provider is true or not - echo "public_provider is ${{ inputs.public_provider }}" - if [ "${{ inputs.public_provider }}" == "none" ]; then - make build - else - make build-and-publish - fi - env: - AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_CERT_NAME: homebrewltd - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: auto - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - CORTEX_API_PORT: ${{ inputs.cortex_api_port }} - - - name: Build app and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - AZURE_CERT_NAME: homebrewltd - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Build app and publish app to github - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true - run: | - make build-and-publish - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }} - AWS_DEFAULT_REGION: auto - AWS_EC2_METADATA_DISABLED: 'true' - AWS_MAX_ATTEMPTS: '5' - AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }} - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} - # AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }} - AZURE_CERT_NAME: homebrewltd - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - - - name: Upload Artifact - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-electron-win-x64-${{ inputs.new_version }} - path: ./electron/dist/*.exe \ No newline at end of file diff --git a/.github/workflows/template-get-update-version.yml b/.github/workflows/template-get-update-version.yml index 70f5eace9..2d992fbec 100644 --- a/.github/workflows/template-get-update-version.yml +++ b/.github/workflows/template-get-update-version.yml @@ -9,7 +9,6 @@ on: jobs: get-update-version: runs-on: ubuntu-latest - environment: production outputs: new_version: ${{ steps.version_update.outputs.new_version }} steps: diff --git a/.github/workflows/template-noti-discord-and-update-url-readme.yml b/.github/workflows/template-noti-discord-and-update-url-readme.yml index eaaee7e50..ce288f541 100644 --- a/.github/workflows/template-noti-discord-and-update-url-readme.yml +++ b/.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -26,7 +26,6 @@ on: jobs: noti-discord-and-update-url-readme: - environment: production runs-on: ubuntu-latest permissions: contents: write diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml index 9356c3f28..303b00109 100644 --- a/.github/workflows/template-tauri-build-linux-x64.yml +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -55,7 +55,6 @@ jobs: DEB_SIG: ${{ steps.packageinfo.outputs.DEB_SIG }} APPIMAGE_SIG: ${{ steps.packageinfo.outputs.APPIMAGE_SIG }} APPIMAGE_FILE_NAME: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} - environment: production permissions: contents: write steps: @@ -96,7 +95,7 @@ jobs: run: | cargo install ctoml - - name: Install Tauri dependecies + - name: Install Tauri dependencies run: | sudo apt update sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 @@ -151,6 +150,12 @@ jobs: fi - name: Build app run: | + # Pin linuxdeploy version to prevent @tauri-apps/cli-linux-x64-gnu from pulling in an outdated version + TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri" + mkdir -p "$TAURI_TOOLKIT_PATH" + wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage" + chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage" + make build-tauri # Copy engines and bun to appimage wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool diff --git a/.github/workflows/template-tauri-build-macos.yml b/.github/workflows/template-tauri-build-macos.yml index 086e14ad2..038a084fa 100644 --- a/.github/workflows/template-tauri-build-macos.yml +++ b/.github/workflows/template-tauri-build-macos.yml @@ -63,7 +63,6 @@ jobs: outputs: MAC_UNIVERSAL_SIG: ${{ steps.metadata.outputs.MAC_UNIVERSAL_SIG }} TAR_NAME: ${{ steps.metadata.outputs.TAR_NAME }} - environment: production permissions: contents: write steps: diff --git a/.vscode/settings.json b/.vscode/settings.json index 9bf4d12b5..78bf4f0ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true + "editor.formatOnSave": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + } } diff --git a/Makefile b/Makefile index 56b50a9d2..6e69c602d 100644 --- a/Makefile +++ b/Makefile @@ -33,23 +33,14 @@ dev: install-and-build yarn copy:lib yarn dev -# Deprecated soon -dev-tauri: install-and-build - yarn install:cortex - yarn download:bin - yarn copy:lib - yarn dev:tauri - # Linting lint: install-and-build yarn lint # Testing test: lint - # yarn build:test - # yarn test:coverage - # Need e2e setup for tauri backend yarn test + yarn test:e2e # Builds and publishes the app build-and-publish: install-and-build diff --git a/core/package.json b/core/package.json index dffc2bcfd..22c815e5b 100644 --- a/core/package.json +++ b/core/package.json @@ -23,20 +23,20 @@ }, "devDependencies": { "@npmcli/arborist": "^7.1.0", - "@types/jest": "^29.5.14", + "@types/jest": "^30.0.0", "@types/node": "^22.10.0", "@types/pacote": "^11.1.7", "@types/request": "^2.48.12", "electron": "33.2.1", "eslint": "8.57.0", "eslint-plugin-jest": "^27.9.0", - "jest": "^29.7.0", + "jest": "^30.0.3", "jest-junit": "^16.0.0", "jest-runner": "^29.7.0", "pacote": "^21.0.0", "request": "^2.88.2", "request-progress": "^3.0.0", - "rimraf": "^3.0.2", + "rimraf": "^6.0.1", "rolldown": "1.0.0-beta.1", "ts-jest": "^29.2.5", "tslib": "^2.6.2", diff --git a/core/src/browser/models/manager.test.ts b/core/src/browser/models/manager.test.ts new file mode 100644 index 000000000..189ca1209 --- /dev/null +++ b/core/src/browser/models/manager.test.ts @@ -0,0 +1,133 @@ +import { ModelManager } from './manager' +import { Model, ModelEvent } from '../../types' +import { events } from '../events' + +jest.mock('../events', () => ({ + events: { + emit: jest.fn(), + }, +})) + +Object.defineProperty(global, 'window', { + value: { + core: {}, + }, + writable: true, +}) + +describe('ModelManager', () => { + let modelManager: ModelManager + let mockModel: Model + + beforeEach(() => { + jest.clearAllMocks() + ;(global.window as any).core = {} + modelManager = new ModelManager() + mockModel = { + id: 'test-model-1', + name: 'Test Model', + version: '1.0.0', + } as Model + }) + + describe('constructor', () => { + it('should set itself on window.core.modelManager when window exists', () => { + expect((global.window as any).core.modelManager).toBe(modelManager) + }) + }) + + describe('register', () => { + it('should register a new model', () => { + modelManager.register(mockModel) + + expect(modelManager.models.has('test-model-1')).toBe(true) + expect(modelManager.models.get('test-model-1')).toEqual(mockModel) + expect(events.emit).toHaveBeenCalledWith(ModelEvent.OnModelsUpdate, {}) + }) + + it('should merge existing model with new model data', () => { + const existingModel: Model = { + id: 'test-model-1', + name: 'Existing Model', + description: 'Existing description', + } as Model + + const updatedModel: Model = { + id: 'test-model-1', + name: 'Updated Model', + version: '2.0.0', + } as Model + + modelManager.register(existingModel) + modelManager.register(updatedModel) + + const registeredModel = modelManager.models.get('test-model-1') + expect(registeredModel).toEqual({ + id: 'test-model-1', + name: 'Existing Model', + description: 'Existing description', + version: '2.0.0', + }) + expect(events.emit).toHaveBeenCalledTimes(2) + }) + }) + + describe('get', () => { + it('should retrieve a registered model by id', () => { + modelManager.register(mockModel) + + const retrievedModel = modelManager.get('test-model-1') + expect(retrievedModel).toEqual(mockModel) + }) + + it('should return undefined for non-existent model', () => { + const retrievedModel = modelManager.get('non-existent-model') + expect(retrievedModel).toBeUndefined() + }) + + it('should return correctly typed model', () => { + modelManager.register(mockModel) + + const retrievedModel = modelManager.get('test-model-1') + expect(retrievedModel?.id).toBe('test-model-1') + expect(retrievedModel?.name).toBe('Test Model') + }) + }) + + describe('instance', () => { + it('should create a new instance when none exists on window.core', () => { + ;(global.window as any).core = {} + + const instance = ModelManager.instance() + expect(instance).toBeInstanceOf(ModelManager) + expect((global.window as any).core.modelManager).toBe(instance) + }) + + it('should return existing instance when it exists on window.core', () => { + const existingManager = new ModelManager() + ;(global.window as any).core.modelManager = existingManager + + const instance = ModelManager.instance() + expect(instance).toBe(existingManager) + }) + }) + + describe('models property', () => { + it('should initialize with empty Map', () => { + expect(modelManager.models).toBeInstanceOf(Map) + expect(modelManager.models.size).toBe(0) + }) + + it('should maintain multiple models', () => { + const model1: Model = { id: 'model-1', name: 'Model 1' } as Model + const model2: Model = { id: 'model-2', name: 'Model 2' } as Model + + modelManager.register(model1) + modelManager.register(model2) + + expect(modelManager.models.size).toBe(2) + expect(modelManager.models.get('model-1')).toEqual(model1) + expect(modelManager.models.get('model-2')).toEqual(model2) + }) + }) +}) \ No newline at end of file diff --git a/core/src/browser/models/utils.test.ts b/core/src/browser/models/utils.test.ts index ac876c3dc..2a1a09d23 100644 --- a/core/src/browser/models/utils.test.ts +++ b/core/src/browser/models/utils.test.ts @@ -29,7 +29,7 @@ describe('validationRules', () => { expect(validationRules.top_k(1)).toBe(true) expect(validationRules.top_k(0)).toBe(true) expect(validationRules.top_k(-0.1)).toBe(false) - expect(validationRules.top_k(1.1)).toBe(false) + expect(validationRules.top_k(1.1)).toBe(true) expect(validationRules.top_k('0.5')).toBe(false) }) @@ -68,8 +68,8 @@ describe('validationRules', () => { expect(validationRules.frequency_penalty(0.5)).toBe(true) expect(validationRules.frequency_penalty(1)).toBe(true) expect(validationRules.frequency_penalty(0)).toBe(true) - expect(validationRules.frequency_penalty(-0.1)).toBe(false) - expect(validationRules.frequency_penalty(1.1)).toBe(false) + expect(validationRules.frequency_penalty(-0.1)).toBe(true) + expect(validationRules.frequency_penalty(1.1)).toBe(true) expect(validationRules.frequency_penalty('0.5')).toBe(false) }) @@ -77,8 +77,8 @@ describe('validationRules', () => { expect(validationRules.presence_penalty(0.5)).toBe(true) expect(validationRules.presence_penalty(1)).toBe(true) expect(validationRules.presence_penalty(0)).toBe(true) - expect(validationRules.presence_penalty(-0.1)).toBe(false) - expect(validationRules.presence_penalty(1.1)).toBe(false) + expect(validationRules.presence_penalty(-0.1)).toBe(true) + expect(validationRules.presence_penalty(1.1)).toBe(true) expect(validationRules.presence_penalty('0.5')).toBe(false) }) @@ -152,6 +152,33 @@ describe('validationRules', () => { expect(validationRules.text_model('true')).toBe(false) expect(validationRules.text_model(1)).toBe(false) }) + + it('should validate repeat_last_n correctly', () => { + expect(validationRules.repeat_last_n(5)).toBe(true) + expect(validationRules.repeat_last_n(-5)).toBe(true) + expect(validationRules.repeat_last_n(0)).toBe(true) + expect(validationRules.repeat_last_n(1.5)).toBe(true) + expect(validationRules.repeat_last_n('5')).toBe(false) + expect(validationRules.repeat_last_n(null)).toBe(false) + }) + + it('should validate repeat_penalty correctly', () => { + expect(validationRules.repeat_penalty(1.1)).toBe(true) + expect(validationRules.repeat_penalty(0.9)).toBe(true) + expect(validationRules.repeat_penalty(0)).toBe(true) + expect(validationRules.repeat_penalty(-1)).toBe(true) + expect(validationRules.repeat_penalty('1.1')).toBe(false) + expect(validationRules.repeat_penalty(null)).toBe(false) + }) + + it('should validate min_p correctly', () => { + expect(validationRules.min_p(0.1)).toBe(true) + expect(validationRules.min_p(0)).toBe(true) + expect(validationRules.min_p(-0.1)).toBe(true) + expect(validationRules.min_p(1.5)).toBe(true) + expect(validationRules.min_p('0.1')).toBe(false) + expect(validationRules.min_p(null)).toBe(false) + }) }) it('should normalize invalid values for keys not listed in validationRules', () => { @@ -192,18 +219,125 @@ describe('normalizeValue', () => { expect(normalizeValue('cpu_threads', '4')).toBe(4) expect(normalizeValue('cpu_threads', 0)).toBe(0) }) + + it('should handle edge cases for normalization', () => { + expect(normalizeValue('ctx_len', -5.7)).toBe(-6) + expect(normalizeValue('token_limit', 'abc')).toBeNaN() + expect(normalizeValue('max_tokens', null)).toBe(0) + expect(normalizeValue('ngl', undefined)).toBeNaN() + expect(normalizeValue('n_parallel', Infinity)).toBe(Infinity) + expect(normalizeValue('cpu_threads', -Infinity)).toBe(-Infinity) + }) + + it('should not normalize non-integer parameters', () => { + expect(normalizeValue('temperature', 1.5)).toBe(1.5) + expect(normalizeValue('top_p', 0.9)).toBe(0.9) + expect(normalizeValue('stream', true)).toBe(true) + expect(normalizeValue('prompt_template', 'template')).toBe('template') + }) }) -it('should handle invalid values correctly by falling back to originParams', () => { - const modelParams = { temperature: 'invalid', token_limit: -1 } - const originParams = { temperature: 0.5, token_limit: 100 } - expect(extractInferenceParams(modelParams as any, originParams)).toEqual(originParams) +describe('extractInferenceParams', () => { + it('should handle invalid values correctly by falling back to originParams', () => { + const modelParams = { temperature: 'invalid', token_limit: -1 } + const originParams = { temperature: 0.5, token_limit: 100 } + expect(extractInferenceParams(modelParams as any, originParams)).toEqual(originParams) + }) + + it('should return an empty object when no modelParams are provided', () => { + expect(extractInferenceParams()).toEqual({}) + }) + + it('should extract and normalize valid inference parameters', () => { + const modelParams = { + temperature: 1.5, + token_limit: 100.7, + top_p: 0.9, + stream: true, + max_tokens: 50.3, + invalid_param: 'should_be_ignored', + } + + const result = extractInferenceParams(modelParams as any) + expect(result).toEqual({ + temperature: 1.5, + token_limit: 100, + top_p: 0.9, + stream: true, + max_tokens: 50, + }) + }) + + it('should handle parameters without validation rules', () => { + const modelParams = { engine: 'llama' } + const result = extractInferenceParams(modelParams as any) + expect(result).toEqual({ engine: 'llama' }) + }) + + it('should skip invalid values when no origin params provided', () => { + const modelParams = { temperature: 'invalid', top_p: 0.8 } + const result = extractInferenceParams(modelParams as any) + expect(result).toEqual({ top_p: 0.8 }) + }) }) -it('should return an empty object when no modelParams are provided', () => { - expect(extractModelLoadParams()).toEqual({}) -}) +describe('extractModelLoadParams', () => { + it('should return an empty object when no modelParams are provided', () => { + expect(extractModelLoadParams()).toEqual({}) + }) -it('should return an empty object when no modelParams are provided', () => { - expect(extractInferenceParams()).toEqual({}) + it('should extract and normalize valid model load parameters', () => { + const modelParams = { + ctx_len: 2048.5, + ngl: 12.7, + embedding: true, + n_parallel: 4.2, + cpu_threads: 8.9, + prompt_template: 'template', + llama_model_path: '/path/to/model', + vision_model: false, + invalid_param: 'should_be_ignored', + } + + const result = extractModelLoadParams(modelParams as any) + expect(result).toEqual({ + ctx_len: 2048, + ngl: 12, + embedding: true, + n_parallel: 4, + cpu_threads: 8, + prompt_template: 'template', + llama_model_path: '/path/to/model', + vision_model: false, + }) + }) + + it('should handle parameters without validation rules', () => { + const modelParams = { + engine: 'llama', + pre_prompt: 'System:', + system_prompt: 'You are helpful', + model_path: '/path', + } + const result = extractModelLoadParams(modelParams as any) + expect(result).toEqual({ + engine: 'llama', + pre_prompt: 'System:', + system_prompt: 'You are helpful', + model_path: '/path', + }) + }) + + it('should fall back to origin params for invalid values', () => { + const modelParams = { ctx_len: -1, ngl: 'invalid' } + const originParams = { ctx_len: 2048, ngl: 12 } + const result = extractModelLoadParams(modelParams as any, originParams) + expect(result).toEqual({}) + }) + + it('should skip invalid values when no origin params provided', () => { + const modelParams = { ctx_len: -1, embedding: true } + const result = extractModelLoadParams(modelParams as any) + expect(result).toEqual({ embedding: true }) + }) }) diff --git a/core/src/browser/models/utils.ts b/core/src/browser/models/utils.ts index 192b838da..c11bed4c7 100644 --- a/core/src/browser/models/utils.ts +++ b/core/src/browser/models/utils.ts @@ -8,18 +8,19 @@ import { ModelParams, ModelRuntimeParams, ModelSettingParams } from '../../types export const validationRules: { [key: string]: (value: any) => boolean } = { temperature: (value: any) => typeof value === 'number' && value >= 0 && value <= 2, token_limit: (value: any) => Number.isInteger(value) && value >= 0, - top_k: (value: any) => typeof value === 'number' && value >= 0 && value <= 1, + top_k: (value: any) => typeof value === 'number' && value >= 0, top_p: (value: any) => typeof value === 'number' && value >= 0 && value <= 1, stream: (value: any) => typeof value === 'boolean', max_tokens: (value: any) => Number.isInteger(value) && value >= 0, stop: (value: any) => Array.isArray(value) && value.every((v) => typeof v === 'string'), - frequency_penalty: (value: any) => typeof value === 'number' && value >= 0 && value <= 1, - presence_penalty: (value: any) => typeof value === 'number' && value >= 0 && value <= 1, + frequency_penalty: (value: any) => typeof value === 'number' && value >= -2 && value <= 2, + presence_penalty: (value: any) => typeof value === 'number' && value >= -2 && value <= 2, repeat_last_n: (value: any) => typeof value === 'number', repeat_penalty: (value: any) => typeof value === 'number', + min_p: (value: any) => typeof value === 'number', ctx_len: (value: any) => Number.isInteger(value) && value >= 0, - ngl: (value: any) => Number.isInteger(value), + ngl: (value: any) => Number.isInteger(value) && value >= 0, embedding: (value: any) => typeof value === 'boolean', n_parallel: (value: any) => Number.isInteger(value) && value >= 0, cpu_threads: (value: any) => Number.isInteger(value) && value >= 0, @@ -49,6 +50,22 @@ export const normalizeValue = (key: string, value: any) => { // Convert to integer return Math.floor(Number(value)) } + if ( + key === 'temperature' || + key === 'top_k' || + key === 'top_p' || + key === 'min_p' || + key === 'repeat_penalty' || + key === 'frequency_penalty' || + key === 'presence_penalty' || + key === 'repeat_last_n' + ) { + // Convert to float + const newValue = parseFloat(value) + if (newValue !== null && !isNaN(newValue)) { + return newValue + } + } return value } diff --git a/docs/src/pages/docs/_assets/llama.cpp-01-updated.png b/docs/src/pages/docs/_assets/llama.cpp-01-updated.png new file mode 100644 index 000000000..177c26145 Binary files /dev/null and b/docs/src/pages/docs/_assets/llama.cpp-01-updated.png differ diff --git a/docs/src/pages/docs/data-folder.mdx b/docs/src/pages/docs/data-folder.mdx index acbbb025b..4c582c801 100644 --- a/docs/src/pages/docs/data-folder.mdx +++ b/docs/src/pages/docs/data-folder.mdx @@ -56,36 +56,37 @@ cd ~/.config/Jan/data # Default install Root directory: `~/jan` + ```sh -/assistants - /jan +/assistants/ + /jan/ assistant.json -/extensions +/engines/ + /llama.cpp/ +/extensions/ extensions.json - /@janhq - /extension_A - package.json -/logs - /app.txt -/models - /model_A - model.yaml - model_A.yaml -/settings - settings.json - /@janhq - /extension_A_Settings - settings.json -/themes - /dark-dimmed - /joi-dark - /joi-light - /night-blue -/threads - /jan_thread_A - messages.jsonl - thread.json - messages.jsonl +/@janhq/ + /assistant-extension/ + /conversational-extension/ + /download-extension/ + /engine-management-extension/ + /hardware-management-extension/ + /inference-cortex-extension/ + /model-extension/ +/files/ +/logs/ + app.log +/models/ + /huggingface.co/ + /Model_Provider_A/ + /Model_A + model_A.gguf + model_A.yaml +/threads/ + /thread_A/ + messages.jsonl + thread.json + ``` ### `assistants/` @@ -93,14 +94,28 @@ Where AI personalities live. The default one (`/assistants/jan/`): ```json { - "avatar": "", + "avatar": "👋", "id": "jan", "object": "assistant", - "created_at": 1715132389207, + "created_at": 1750945742.536, "name": "Jan", - "description": "A default assistant that can use all downloaded models", + "description": "Jan is a helpful AI assistant that can use tools and help complete tasks for its users.", "model": "*", - "instructions": "" + "instructions": "You have access to a set of tools to help you answer the user’s question. You can use only one tool per message, and you’ll receive the result of that tool in the user’s next response. To complete a task, use tools step by step—each step should be guided by the outcome of the previous one.\nTool Usage Rules:\n1. Always provide the correct values as arguments when using tools. Do not pass variable names—use actual values instead.\n2. You may perform multiple tool steps to complete a task.\n3. Avoid repeating a tool call with exactly the same parameters to prevent infinite loops.", + "tools": [ + { + "type": "retrieval", + "enabled": false, + "useTimeWeightedRetriever": false, + "settings": { + "top_k": 2, + "chunk_size": 1024, + "chunk_overlap": 64, + "retrieval_template": "Use the following pieces of context to answer the question at the end.\n----------------\nCONTEXT: {CONTEXT}\n----------------\nQUESTION: {QUESTION}\n----------------\nHelpful Answer:" + } + } + ], + "file_ids": [] } ``` @@ -140,88 +155,65 @@ Debugging headquarters (`/logs/app.txt`): The silicon brain collection. Each model has its own `model.json`. -Full parameters: [here](/docs/models/model-parameters) +Full parameters: [here](/docs/model-parameters) -### `settings/` -Control panel. Extension settings in `/settings/@janhq/`: - -| Parameter | Description | -|----------------|----------------------------------------------------| -| key | Setting identifier | -| title | Display name | -| description | Setting explanation | -| controllerType | UI component type | -| controllerProps| Component properties | -| extensionName | Parent extension link | - -GPU settings (`settings.json`): - -| Parameter | Description | -|----------------------|--------------------------------------------| -| notify | Notification status | -| run_mode | Operating mode | -| nvidia_driver.exist | NVIDIA driver presence | -| nvidia_driver.version| Driver version | -| cuda.exist | CUDA availability | -| cuda.version | CUDA version | -| gpus[0].id | GPU identifier | -| gpus[0].vram | GPU memory (MB) | -| gpus[0].name | GPU model | -| gpus[0].arch | GPU architecture | -| gpu_highest_vram | Most capable GPU | -| gpus_in_use | Active GPUs | -| is_initial | First run flag | -| vulkan | Vulkan support | - -### `themes/` -Visual wardrobe. Each theme's `theme.json`: - -| Parameter | Description | -|------------------|-------------------------------------------| -| id | Theme identifier | -| displayName | UI name | -| reduceTransparent| Transparency control | -| nativeTheme | OS theme sync | -| variables | Component settings | - ### `threads/` Chat archive. Each thread (`/threads/jan_unixstamp/`) contains: - `messages.jsonl`: ```json - { - "id":"01J6Y6FH8PFTHQB5PNJTHEN27C", - "thread_id":"jan_1725437954", - "type":"Thread", - "role":"assistant", - "content": - [ - { - "type": "text", - "text": { - "value": "Hello! Is there something I can help you with or would you like to chat?", - "annotations": [] - } - } - ], - "status": "ready", - "created": 1725442802966, - "updated": 1725442802966, - "object": "thread.message" - } + { + "completed_at": 0, + "content": [ + { + "text": { + "annotations": [], + "value": "Hello! I can help you with various tasks. I can search for information on the internet, including news, videos, images, shopping, and more. I can also scrape webpages to extract specific information. Let me know what you need!" + }, + "type": "text" + } + ], + "created_at": 1751012639307, + "id": "01JYR7S0JB5ZBGMJV52KWMW5VW", + "metadata": { + "assistant": { + "avatar": "👋", + "id": "jan", + "instructions": "You have access to a set of tools to help you answer the user's question. You can use only one tool per message, and you'll receive the result of that tool in the user's next response. To complete a task, use tools step by step—each step should be guided by the outcome of the previous one.\nTool Usage Rules:\n1. Always provide the correct values as arguments when using tools. Do not pass variable names—use actual values instead.\n2. You may perform multiple tool steps to complete a task.\n3. Avoid repeating a tool call with exactly the same parameters to prevent infinite loops.", + "name": "Jan", + "parameters": "" + }, + "tokenSpeed": { + "lastTimestamp": 1751012637097, + "message": "01JYR7S0GW5M9PSHMRE7T8VQJM", + "tokenCount": 49, + "tokenSpeed": 22.653721682847895 + } + }, + "object": "thread.message", + "role": "assistant", + "status": "ready", + "thread_id": "8f2c9922-db49-4d1e-8620-279c05baf2d0", + "type": "text" + } ``` - `thread.json`: | Parameter | Description | |------------|------------------------------------------------| +| assistants | Assistant configuration clone | +| created | Creation timestamp | | id | Thread identifier | +| metadata | Additional thread data | +| model | Active model settings | | object | OpenAI compatibility marker | | title | Thread name | -| assistants | Assistant configuration clone | -| model | Active model settings | -| metadata | Additional thread data | +| updated | Updated timestamp | + + + ## Delete Jan Data Uninstall guides: [Mac](/docs/desktop/mac#step-2-clean-up-data-optional), diff --git a/docs/src/pages/docs/llama-cpp.mdx b/docs/src/pages/docs/llama-cpp.mdx index 2b4d76738..b76c42ea3 100644 --- a/docs/src/pages/docs/llama-cpp.mdx +++ b/docs/src/pages/docs/llama-cpp.mdx @@ -33,7 +33,7 @@ import { Settings, EllipsisVertical, Plus, FolderOpen, Pencil } from 'lucide-rea Jan uses **llama.cpp** for running local AI models. You can find its settings in **Settings** () > **Local Engine** > **llama.cpp**:
-![llama.cpp](./_assets/llama.cpp-01.png) +![llama.cpp](./_assets/llama.cpp-01-updated.png)
These settings are for advanced users, you would want to check these settings when: @@ -151,6 +151,7 @@ For detailed hardware compatibility, please visit our guide for [Mac](/docs/desk | **Caching** | - Enable to store recent prompts and responses

- Improves response time for repeated prompts | Enabled | | **KV Cache Type** | - KV cache implementation type; controls memory usage and precision trade-off

- Options:

• f16 (most stable)

• q8_0 (balanced)

• q4_0 (lowest memory) | f16 | | **mmap** | - Enables memory-mapped model loading

- Reduces memory usage

- Recommended for large models | Enabled | +| **Context Shift** | - Automatically shifts the context window when the model is unable to process the entire prompt
- Ensures that the most relevant information is always included
- Recommended for long conversations and multiple tool calls | Disabled | ## Best Practices diff --git a/docs/src/pages/docs/model-parameters.mdx b/docs/src/pages/docs/model-parameters.mdx index 86846a8ea..56286bcf4 100644 --- a/docs/src/pages/docs/model-parameters.mdx +++ b/docs/src/pages/docs/model-parameters.mdx @@ -38,8 +38,6 @@ These settings are available in the model settings modal: | **Repeat Last N** | Number of tokens to consider for repeat penalty. | | **Repeat Penalty** | Penalize repeating token sequences. | | **Presence Penalty**| Penalize alpha presence (encourages new topics). | -| **Max Tokens** | Maximum length of the model's response. | -| **Stop Sequences** | Tokens or phrases that will end the model's response. | | **Frequency Penalty** | Reduces word repetition. |
diff --git a/docs/src/pages/docs/server-examples/continue-dev.mdx b/docs/src/pages/docs/server-examples/continue-dev.mdx index c6b9d50a8..a982697ba 100644 --- a/docs/src/pages/docs/server-examples/continue-dev.mdx +++ b/docs/src/pages/docs/server-examples/continue-dev.mdx @@ -36,11 +36,15 @@ Follow this [guide](https://continue.dev/docs/quickstart) to install the Continu To set up Continue for use with Jan's Local Server, you must activate the Jan API Server with your chosen model. -1. Press the `<>` button. Jan will take you to the **Local API Server** section. +1. Press the `⚙️ Settings` button. -2. Setup the server, which includes the **IP Port**, **Cross-Origin-Resource-Sharing (CORS)** and **Verbose Server Logs**. +2. Locate `Local API Server`. -3. Press the **Start Server** button +3. Setup the server, which includes the **IP Port**, **Cross-Origin-Resource-Sharing (CORS)** and **Verbose Server Logs**. + +4. Include your user-defined API Key. + +5. Press the **Start Server** button ### Step 3: Configure Continue to Use Jan's Local Server @@ -64,30 +68,35 @@ To set up Continue for use with Jan's Local Server, you must activate the Jan AP -```json title="~/.continue/config.json" -{ - "models": [ - { - "title": "Jan", - "provider": "openai", - "model": "mistral-ins-7b-q4", - "apiKey": "EMPTY", - "apiBase": "http://localhost:1337/v1" - } - ] -} +```yaml title="~/.continue/config.yaml" +name: Local Assistant +version: 1.0.0 +schema: v1 +models: + - name: Jan + provider: openai + model: #MODEL_NAME (e.g. qwen3:0.6b) + apiKey: #YOUR_USER_DEFINED_API_KEY_HERE (e.g. hello) + apiBase: http://localhost:1337/v1 +context: + - provider: code + - provider: docs + - provider: diff + - provider: terminal + - provider: problems + - provider: folder + - provider: codebase ``` 2. Ensure the file has the following configurations: - Ensure `openai` is selected as the `provider`. - Match the `model` with the one enabled in the Jan API Server. - - Set `apiBase` to `http://localhost:1337`. - - Leave the `apiKey` field to `EMPTY`. + - Set `apiBase` to `http://localhost:1337/v1`. ### Step 4: Ensure the Using Model Is Activated in Jan -1. Navigate to `Settings` > `My Models`. -2. Click the **three dots (⋮)** button. +1. Navigate to `Settings` > `Model Providers`. +2. Under Llama.cpp, find the model that you would want to use. 3. Select the **Start Model** button to activate the model. diff --git a/extensions/assistant-extension/package.json b/extensions/assistant-extension/package.json index 4761aa900..f17d42588 100644 --- a/extensions/assistant-extension/package.json +++ b/extensions/assistant-extension/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "cpx": "^1.5.0", - "rimraf": "^3.0.2", + "rimraf": "^6.0.1", "rolldown": "1.0.0-beta.1", "run-script-os": "^1.1.6", "typescript": "^5.3.3" diff --git a/extensions/conversational-extension/package.json b/extensions/conversational-extension/package.json index abb76e4d0..26ba21b9d 100644 --- a/extensions/conversational-extension/package.json +++ b/extensions/conversational-extension/package.json @@ -17,7 +17,7 @@ }, "devDependencies": { "cpx": "^1.5.0", - "rimraf": "^3.0.2", + "rimraf": "^6.0.1", "rolldown": "1.0.0-beta.1", "ts-loader": "^9.5.0", "typescript": "^5.7.2" diff --git a/extensions/download-extension/package.json b/extensions/download-extension/package.json index 1c3f2f174..f15f12bdb 100644 --- a/extensions/download-extension/package.json +++ b/extensions/download-extension/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "cpx": "^1.5.0", - "rimraf": "^3.0.2", + "rimraf": "^6.0.1", "rolldown": "1.0.0-beta.1", "run-script-os": "^1.1.6", "typescript": "5.8.3", diff --git a/package.json b/package.json index 4d9f8382e..7be0e769d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "workspaces": { "packages": [ "core", - "web-app" + "web-app", + "tests-e2e-js" ] }, "scripts": { @@ -13,6 +14,11 @@ "build": "yarn build:web && yarn build:tauri", "test": "yarn workspace @janhq/web-app test", "test:coverage": "yarn workspace @janhq/web-app test", + "test:prepare": "yarn build:icon && yarn copy:lib && yarn copy:assets:tauri && yarn build --no-bundle ", + "test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test", + "test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test", + "test:e2e:darwin": "echo 'E2E tests are not supported on macOS yet due to WebDriver limitations'", + "test:e2e": "run-script-os", "dev:web": "yarn workspace @janhq/web-app dev", "dev:tauri": "CLEAN=true yarn build:icon && yarn copy:assets:tauri && tauri dev", "install:cortex:linux:darwin": "cd src-tauri/binaries && ./download.sh", @@ -39,7 +45,7 @@ "cpx": "^1.5.0", "cross-env": "^7.0.3", "husky": "^9.1.5", - "jest": "^29.7.0", + "jest": "^30.0.3", "jest-environment-jsdom": "^29.7.0", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c55c8ea4e..7068ffba6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -19,8 +19,8 @@ tauri-build = { version = "2.0.2", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } log = "0.4" -tauri = { version = "2.4.0", features = [ "protocol-asset", "macos-private-api", - "test", +tauri = { version = "2.5.0", features = [ "protocol-asset", "macos-private-api", + "test" ] } tauri-plugin-log = "2.0.0-rc" tauri-plugin-shell = "2.2.0" diff --git a/src-tauri/src/core/setup.rs b/src-tauri/src/core/setup.rs index 8d8a3d557..42ee0faa5 100644 --- a/src-tauri/src/core/setup.rs +++ b/src-tauri/src/core/setup.rs @@ -277,7 +277,12 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> { ]); #[cfg(target_os = "windows")] { - let resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + let mut resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + // If debug + #[cfg(debug_assertions)] + { + resource_dir = resource_dir.join("binaries"); + } let normalized_path = resource_dir.to_string_lossy().replace(r"\\?\", ""); let normalized_pathbuf = PathBuf::from(normalized_path); cmd = cmd.current_dir(normalized_pathbuf); @@ -286,12 +291,12 @@ pub fn setup_sidecar(app: &App) -> Result<(), String> { #[cfg(not(target_os = "windows"))] { cmd = cmd.env("LD_LIBRARY_PATH", { - let current_app_data_dir = app_handle_for_spawn - .path() - .resource_dir() - .unwrap() - .join("binaries"); - let dest = current_app_data_dir.to_str().unwrap(); + let mut resource_dir = app_handle_for_spawn.path().resource_dir().unwrap(); + #[cfg(not(debug_assertions))] + { + resource_dir = resource_dir.join("binaries"); + } + let dest = resource_dir.to_str().unwrap(); let ld_path_env = std::env::var("LD_LIBRARY_PATH").unwrap_or_default(); format!("{}{}{}", ld_path_env, ":", dest) }); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 04191e842..210322297 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -17,6 +17,8 @@ "label": "main", "title": "Jan", "width": 1024, + "minWidth": 375, + "minHeight": 667, "height": 800, "resizable": true, "fullscreen": false, diff --git a/tests-e2e-js/.gitignore b/tests-e2e-js/.gitignore new file mode 100644 index 000000000..1521c8b76 --- /dev/null +++ b/tests-e2e-js/.gitignore @@ -0,0 +1 @@ +dist diff --git a/tests-e2e-js/package.json b/tests-e2e-js/package.json new file mode 100644 index 000000000..e7dc18587 --- /dev/null +++ b/tests-e2e-js/package.json @@ -0,0 +1,23 @@ +{ + "name": "tests-e2-js", + "version": "0.0.0", + "private": true, + "type": "module", + "main": "src/main.ts", + "scripts": { + "build": "tsc", + "test": "node --test --test-force-exit --loader ts-node/esm ./src/main.ts" + }, + "dependencies": { + "@tauri-e2e/selenium": "0.2.2", + "log4js": "^6.9.1", + "selenium-webdriver": "^4.22.0", + "ts-node": "^10.9.2" + }, + "devDependencies": { + "@types/node": "^20.14.9", + "@types/selenium-webdriver": "^4.1.28", + "tsimp": "^2.0.11", + "typescript": "^5.5.2" + } +} diff --git a/tests-e2e-js/src/main.ts b/tests-e2e-js/src/main.ts new file mode 100644 index 000000000..c6e0aeba6 --- /dev/null +++ b/tests-e2e-js/src/main.ts @@ -0,0 +1,51 @@ +import assert from 'node:assert' +import { ChildProcess } from 'node:child_process' +import { afterEach, beforeEach, describe, test } from 'node:test' +import { By, until, WebDriver } from 'selenium-webdriver' +import * as e2e from '@tauri-e2e/selenium' +import { default as log4js } from 'log4js' + +let logger = log4js.getLogger() +logger.level = 'debug' + +process.env.TAURI_WEBDRIVER_LOGLEVEL = 'debug' +process.env.TAURI_WEBDRIVER_BINARY = await e2e.install.PlatformDriver() +process.env.TAURI_SELENIUM_BINARY = '../src-tauri/target/release/Jan.exe' +process.env.SELENIUM_REMOTE_URL = 'http://127.0.0.1:6655' + +//@ts-ignore fuck you javascript +e2e.setLogger(logger) + +describe('Tauri E2E tests', async () => { + let driver: WebDriver + let webDriver: ChildProcess + + beforeEach(async () => { + // Spawn WebDriver process. + webDriver = await e2e.launch.spawnWebDriver() + // wait 1 second + await new Promise((r) => setTimeout(r, 1000)) + // Create driver session. + driver = new e2e.selenium.Builder().build() + // Wait for the body element to be present + // await driver.wait(until.elementLocated({ css: 'body' })) + }) + + afterEach(async () => { + await e2e.selenium.cleanupSession(driver) + e2e.launch.killWebDriver(webDriver) + }) + + test('Find hub', async () => { + const hub = until.elementLocated(By.css('[data-test-id="menu-common:hub"')) + // console.log('GG', hub) + // @ts-ignore + await driver.wait(hub.fn, 120000) + + const menuElement = await driver.findElement({ + css: '[data-test-id="menu-common:hub"]', + }) + assert(menuElement !== null, 'Hub menu element should be available') + await menuElement.isDisplayed() + }) +}) diff --git a/tests-e2e-js/tsconfig.json b/tests-e2e-js/tsconfig.json new file mode 100644 index 000000000..6189f7d38 --- /dev/null +++ b/tests-e2e-js/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "src/*.ts" + ], + "exclude": [ + "node_modules", + "dist" + ], +} diff --git a/web-app/package.json b/web-app/package.json index 340104f00..4874b310c 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -66,7 +66,7 @@ "remark-math": "^6.0.0", "sonner": "^2.0.3", "tailwindcss": "^4.1.4", - "token.js": "npm:token.js-fork@0.7.9", + "token.js": "npm:token.js-fork@0.7.12", "tw-animate-css": "^1.2.7", "ulidx": "^2.4.1", "unified": "^11.0.5", diff --git a/web-app/src/components/ui/dialog.tsx b/web-app/src/components/ui/dialog.tsx index 98f4da71c..ed95f00b4 100644 --- a/web-app/src/components/ui/dialog.tsx +++ b/web-app/src/components/ui/dialog.tsx @@ -67,7 +67,7 @@ function DialogContent({ data-slot="dialog-content" aria-describedby={ariaDescribedBy} className={cn( - 'bg-main-view max-h-[calc(100%-48px)] overflow-auto border-main-view-fg/10 text-main-view-fg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg', + 'bg-main-view max-h-[calc(100%-80px)] overflow-auto border-main-view-fg/10 text-main-view-fg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg', className )} {...props} diff --git a/web-app/src/constants/routes.ts b/web-app/src/constants/routes.ts index db741e9e6..e9997590a 100644 --- a/web-app/src/constants/routes.ts +++ b/web-app/src/constants/routes.ts @@ -5,6 +5,7 @@ export const route = { assistant: '/assistant', settings: { index: '/settings', + model_providers: '/settings/providers', providers: '/settings/providers/$providerName', general: '/settings/general', appearance: '/settings/appearance', diff --git a/web-app/src/containers/Card.tsx b/web-app/src/containers/Card.tsx index 324b98b2d..7d68550db 100644 --- a/web-app/src/containers/Card.tsx +++ b/web-app/src/containers/Card.tsx @@ -10,43 +10,63 @@ type CardProps = { type CardItemProps = { title?: string | ReactNode description?: string | ReactNode + descriptionOutside?: string | ReactNode align?: 'start' | 'center' | 'end' actions?: ReactNode column?: boolean className?: string + classNameWrapperAction?: string } export function CardItem({ title, description, + descriptionOutside, className, + classNameWrapperAction, align = 'center', column, actions, }: CardItemProps) { return ( -
-
-

{title}

- {description && ( - - {description} - + <> +
+
+

{title}

+ {description && ( + + {description} + + )} +
+ {actions && ( +
+ {actions} +
)}
- {actions && ( -
{actions}
+ {descriptionOutside && ( + + {descriptionOutside} + )} -
+ ) } diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 2d16b2eee..0cecb2bf3 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -365,6 +365,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { rows={1} maxRows={10} value={prompt} + data-test-id={'chat-input'} onChange={(e) => { setPrompt(e.target.value) // Count the number of newlines to estimate rows @@ -567,6 +568,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { variant={!prompt.trim() ? null : 'default'} size="icon" disabled={!prompt.trim()} + data-test-id="send-message-button" onClick={() => handleSendMesage(prompt)} > {streamingContent ? ( diff --git a/web-app/src/containers/ChatWidthSwitcher.tsx b/web-app/src/containers/ChatWidthSwitcher.tsx index 27cc3c69d..10417200e 100644 --- a/web-app/src/containers/ChatWidthSwitcher.tsx +++ b/web-app/src/containers/ChatWidthSwitcher.tsx @@ -9,27 +9,31 @@ export function ChatWidthSwitcher() { const { t } = useTranslation() return ( -
+
- {!IS_MACOS && ( -
- - setSearchTerm(e.target.value)} - /> - {searchTerm && ( - - )} -
- )} -
+ // Disable body scroll when panel is open on small screens + useEffect(() => { + if (isSmallScreen && open) { + document.body.style.overflow = 'hidden' + } else { + document.body.style.overflow = '' + } -
-
- {IS_MACOS && ( -
+ return () => { + document.body.style.overflow = '' + } + }, [isSmallScreen, open]) + + return ( + <> + {/* Backdrop overlay for small screens */} + {isSmallScreen && open && ( +
{ + // Don't close if clicking on search container or if currently searching + if ( + searchContainerRef.current?.contains(e.target as Node) || + searchContainerMacRef.current?.contains(e.target as Node) + ) { + return + } + setLeftPanel(false) + }} + /> + )} + + + ) } diff --git a/web-app/src/containers/ProvidersMenu.tsx b/web-app/src/containers/ProvidersMenu.tsx deleted file mode 100644 index 7da1d2f83..000000000 --- a/web-app/src/containers/ProvidersMenu.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { route } from '@/constants/routes' -import { useModelProvider } from '@/hooks/useModelProvider' -import { cn, getProviderTitle } from '@/lib/utils' -import { useNavigate, useMatches, Link } from '@tanstack/react-router' -import { IconArrowLeft, IconCirclePlus } from '@tabler/icons-react' -import { - Dialog, - DialogClose, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/components/ui/dialog' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { useCallback, useState } from 'react' -import { openAIProviderSettings } from '@/mock/data' -import ProvidersAvatar from '@/containers/ProvidersAvatar' -import cloneDeep from 'lodash/cloneDeep' -import { toast } from 'sonner' -import { useTranslation } from '@/i18n/react-i18next-compat' - -const ProvidersMenu = ({ - stepSetupRemoteProvider, -}: { - stepSetupRemoteProvider: boolean -}) => { - const { providers, addProvider } = useModelProvider() - const navigate = useNavigate() - const matches = useMatches() - const [name, setName] = useState('') - const { t } = useTranslation() - - const createProvider = useCallback(() => { - if (providers.some((e) => e.provider === name)) { - toast.error(t('providerAlreadyExists', { name })) - return - } - const newProvider = { - provider: name, - active: true, - models: [], - settings: cloneDeep(openAIProviderSettings) as ProviderSetting[], - api_key: '', - base_url: 'https://api.openai.com/v1', - } - addProvider(newProvider) - setTimeout(() => { - navigate({ - to: route.settings.providers, - params: { - providerName: name, - }, - }) - }, 0) - }, [providers, name, addProvider, t, navigate]) - - return ( -
- -
- - {t('common:back')} -
- -
- {providers.map((provider, index) => { - const isActive = matches.some( - (match) => - match.routeId === '/settings/providers/$providerName' && - 'providerName' in match.params && - match.params.providerName === provider.provider - ) - - return ( -
- -
- ) - })} - - - -
- - {t('provider:addProvider')} -
-
- - - {t('provider:addOpenAIProvider')} - setName(e.target.value)} - className="mt-2" - placeholder={t('provider:enterNameForProvider')} - onKeyDown={(e) => { - // Prevent key from being captured by parent components - e.stopPropagation() - }} - /> - - - - - - - - - - -
-
-
- ) -} - -export default ProvidersMenu diff --git a/web-app/src/containers/SettingsMenu.tsx b/web-app/src/containers/SettingsMenu.tsx index 5d5e3ef5b..8aea3b501 100644 --- a/web-app/src/containers/SettingsMenu.tsx +++ b/web-app/src/containers/SettingsMenu.tsx @@ -1,20 +1,56 @@ -import { Link, useMatches } from '@tanstack/react-router' +import { Link } from '@tanstack/react-router' import { route } from '@/constants/routes' import { useTranslation } from '@/i18n/react-i18next-compat' -import { useModelProvider } from '@/hooks/useModelProvider' +import { useState, useEffect } from 'react' +import { + IconChevronDown, + IconChevronRight, + IconMenu2, + IconX, +} from '@tabler/icons-react' +import { useMatches, useNavigate } from '@tanstack/react-router' +import { cn } from '@/lib/utils' + import { useGeneralSetting } from '@/hooks/useGeneralSetting' +import { useModelProvider } from '@/hooks/useModelProvider' +import { getProviderTitle } from '@/lib/utils' +import ProvidersAvatar from '@/containers/ProvidersAvatar' const SettingsMenu = () => { const { t } = useTranslation() - const { providers } = useModelProvider() - const { experimentalFeatures } = useGeneralSetting() - const firstItemProvider = - providers.length > 0 ? providers[0].provider : 'llama.cpp' + const [expandedProviders, setExpandedProviders] = useState(false) + const [isMenuOpen, setIsMenuOpen] = useState(false) const matches = useMatches() - const isActive = matches.some( + const navigate = useNavigate() + + const { experimentalFeatures } = useGeneralSetting() + const { providers } = useModelProvider() + + // Filter providers that have active API keys (or are llama.cpp which doesn't need one) + const activeProviders = providers.filter((provider) => provider.active) + + // Check if current route has a providerName parameter and expand providers submenu + useEffect(() => { + const hasProviderName = matches.some( + (match) => + match.routeId === '/settings/providers/$providerName' && + 'providerName' in match.params + ) + const isProvidersRoute = matches.some( + (match) => match.routeId === '/settings/providers/' + ) + if (hasProviderName || isProvidersRoute) { + setExpandedProviders(true) + } + }, [matches]) + + // Check if we're in the setup remote provider step + const stepSetupRemoteProvider = matches.some( (match) => - match.routeId === '/settings/providers/$providerName' && - 'providerName' in match.params + match.search && + typeof match.search === 'object' && + 'step' in match.search && + match.search.step === 'setup_remote_provider' ) const menuSettings = [ @@ -30,6 +66,11 @@ const SettingsMenu = () => { title: 'common:privacy', route: route.settings.privacy, }, + { + title: 'common:modelProviders', + route: route.settings.model_providers, + hasSubMenu: activeProviders.length > 0, + }, { title: 'common:keyboardShortcuts', route: route.settings.shortcuts, @@ -61,52 +102,113 @@ const SettingsMenu = () => { }, ] + const toggleProvidersExpansion = () => { + setExpandedProviders(!expandedProviders) + } + + const toggleMenu = () => { + setIsMenuOpen(!isMenuOpen) + } + return ( -
-
- {menuSettings.map((menu, index) => { - // Render the menu item - const menuItem = ( - - {t(menu.title)} - - ) + <> + +
+
+ {menuSettings.map((menu) => ( +
+ +
+ {t(menu.title)} + {menu.hasSubMenu && ( + + )} +
+ - if (index === 2) { - return ( -
- {menuItem} + {/* Sub-menu for model providers */} + {menu.hasSubMenu && expandedProviders && ( +
+ {activeProviders.map((provider) => { + const isActive = matches.some( + (match) => + match.routeId === '/settings/providers/$providerName' && + 'providerName' in match.params && + match.params.providerName === provider.provider + ) - {/* Model Providers Link with default parameter */} - {isActive ? ( -
- {t('common:modelProviders')} -
- ) : ( - - - {t('common:modelProviders')} - - - )} -
- ) - } - - // For other menu items, just render them normally - return menuItem - })} + return ( +
+ +
+ ) + })} +
+ )} +
+ ))} +
-
+ ) } diff --git a/web-app/src/containers/ThreadList.tsx b/web-app/src/containers/ThreadList.tsx index e4ed8aa93..bd3825eee 100644 --- a/web-app/src/containers/ThreadList.tsx +++ b/web-app/src/containers/ThreadList.tsx @@ -20,8 +20,10 @@ import { IconStar, } from '@tabler/icons-react' import { useThreads } from '@/hooks/useThreads' +import { useLeftPanel } from '@/hooks/useLeftPanel' import { cn } from '@/lib/utils' import { route } from '@/constants/routes' +import { useSmallScreen } from '@/hooks/useMediaQuery' import { DropdownMenu, @@ -55,6 +57,9 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { isDragging, } = useSortable({ id: thread.id, disabled: true }) + const isSmallScreen = useSmallScreen() + const { setLeftPanel } = useLeftPanel() + const style = { transform: CSS.Transform.toString(transform), transition, @@ -75,7 +80,11 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { const handleClick = () => { if (!isDragging) { - navigate({ to: route.threadsDetail, params: { threadId: thread.id } }) + // Only close panel and navigate if the thread is not already active + if (!isActive) { + if (isSmallScreen) setLeftPanel(false) + navigate({ to: route.threadsDetail, params: { threadId: thread.id } }) + } } } @@ -85,7 +94,9 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { return (thread.title || '').replace(/]*>|<\/span>/g, '') }, [thread.title]) - const [title, setTitle] = useState(plainTitleForRename || t('common:newThread')) + const [title, setTitle] = useState( + plainTitleForRename || t('common:newThread') + ) return (
{ setOpenDropdown(false) toast.success(t('common:toast.renameThread.title'), { id: 'rename-thread', - description: t('common:toast.renameThread.description', { title }), + description: t( + 'common:toast.renameThread.description', + { title } + ), }) }} > @@ -231,7 +245,9 @@ const SortableItem = memo(({ thread }: { thread: Thread }) => { setOpenDropdown(false) toast.success(t('common:toast.deleteThread.title'), { id: 'delete-thread', - description: t('common:toast.deleteThread.description'), + description: t( + 'common:toast.deleteThread.description' + ), }) setTimeout(() => { navigate({ to: route.home }) diff --git a/web-app/src/containers/dialogs/AddEditAssistant.tsx b/web-app/src/containers/dialogs/AddEditAssistant.tsx index 7a1242027..f4e327e35 100644 --- a/web-app/src/containers/dialogs/AddEditAssistant.tsx +++ b/web-app/src/containers/dialogs/AddEditAssistant.tsx @@ -378,73 +378,27 @@ export default function AddEditAssistant({
{paramsKeys.map((key, index) => ( -
- - handleParameterChange(index, e.target.value, 'key') - } - placeholder={t('assistants:key')} - className="w-24" - /> +
+
+ + handleParameterChange(index, e.target.value, 'key') + } + placeholder={t('assistants:key')} + className="w-full sm:w-24" + /> - - -
- - -
-
- - - handleParameterChange(index, 'string', 'type') - } - > - {t('assistants:stringValue')} - - - handleParameterChange(index, 'number', 'type') - } - > - {t('assistants:numberValue')} - - - handleParameterChange(index, 'boolean', 'type') - } - > - {t('assistants:booleanValue')} - - - handleParameterChange(index, 'json', 'type') - } - > - {t('assistants:jsonValue')} - - -
- - {paramsTypes[index] === 'boolean' ? ( -
+
@@ -454,48 +408,98 @@ export default function AddEditAssistant({ />
- + - handleParameterChange(index, true, 'value') + handleParameterChange(index, 'string', 'type') } > - {t('assistants:trueValue')} + {t('assistants:stringValue')} - handleParameterChange(index, false, 'value') + handleParameterChange(index, 'number', 'type') } > - {t('assistants:falseValue')} + {t('assistants:numberValue')} + + + handleParameterChange(index, 'boolean', 'type') + } + > + {t('assistants:booleanValue')} + + + handleParameterChange(index, 'json', 'type') + } + > + {t('assistants:jsonValue')} - ) : paramsTypes[index] === 'json' ? ( - - handleParameterChange(index, e.target.value, 'value') - } - placeholder={t('assistants:jsonValuePlaceholder')} - className="flex-1" - /> - ) : ( - - handleParameterChange(index, e.target.value, 'value') - } - type={paramsTypes[index] === 'number' ? 'number' : 'text'} - placeholder={t('assistants:value')} - className="flex-1" - /> - )} + {paramsTypes[index] === 'boolean' ? ( + + +
+ + +
+
+ + + handleParameterChange(index, true, 'value') + } + > + {t('assistants:trueValue')} + + + handleParameterChange(index, false, 'value') + } + > + {t('assistants:falseValue')} + + +
+ ) : paramsTypes[index] === 'json' ? ( + + handleParameterChange(index, e.target.value, 'value') + } + placeholder={t('assistants:jsonValuePlaceholder')} + className="sm:flex-1 h-[36px] w-full" + /> + ) : ( + + handleParameterChange(index, e.target.value, 'value') + } + type={paramsTypes[index] === 'number' ? 'number' : 'text'} + placeholder={t('assistants:value')} + className="sm:flex-1 h-[36px] w-full" + /> + )} +
handleRemoveParameter(index)} diff --git a/web-app/src/containers/dialogs/CortexFailureDialog.tsx b/web-app/src/containers/dialogs/CortexFailureDialog.tsx index b28281f54..48d08569d 100644 --- a/web-app/src/containers/dialogs/CortexFailureDialog.tsx +++ b/web-app/src/containers/dialogs/CortexFailureDialog.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { listen } from '@tauri-apps/api/event' import { invoke } from '@tauri-apps/api/core' -import { t } from 'i18next' + import { Dialog, DialogContent, @@ -11,8 +11,10 @@ import { DialogTitle, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' +import { useTranslation } from '@/i18n' export function CortexFailureDialog() { + const { t } = useTranslation() const [showDialog, setShowDialog] = useState(false) useEffect(() => { @@ -52,15 +54,10 @@ export function CortexFailureDialog() { - - {t('cortexFailureDialog.title', 'Local AI Engine Issue')} - + {t('cortexFailureDialog.title')} - {t( - 'cortexFailureDialog.description', - 'The local AI engine (Cortex) failed to start after multiple attempts. This might prevent some features from working correctly.' - )} + {t('cortexFailureDialog.description')} diff --git a/web-app/src/hooks/useClickOutside.ts b/web-app/src/hooks/useClickOutside.ts new file mode 100644 index 000000000..237b9dff1 --- /dev/null +++ b/web-app/src/hooks/useClickOutside.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useEffect, useRef } from 'react' + +const DEFAULT_EVENTS = ['mousedown', 'touchstart'] + +export function useClickOutside( + handler: () => void, + events?: string[] | null, + nodes?: (HTMLElement | null)[] +) { + const ref = useRef(null) + + useEffect(() => { + const listener = (event: any) => { + const { target } = event ?? {} + if (Array.isArray(nodes)) { + const shouldIgnore = + target?.hasAttribute('data-ignore-outside-clicks') || + (!document.body.contains(target) && target.tagName !== 'HTML') + const shouldTrigger = nodes.every( + (node) => !!node && !event.composedPath().includes(node) + ) + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + shouldTrigger && !shouldIgnore && handler() + } else if (ref.current && !ref.current.contains(target)) { + handler() + } + } + + ;(events || DEFAULT_EVENTS).forEach((fn) => + document.addEventListener(fn, listener) + ) + + return () => { + ;(events || DEFAULT_EVENTS).forEach((fn) => + document.removeEventListener(fn, listener) + ) + } + }, [ref, handler, nodes, events]) + + return ref +} diff --git a/web-app/src/hooks/useMediaQuery.ts b/web-app/src/hooks/useMediaQuery.ts new file mode 100644 index 000000000..9e479eec4 --- /dev/null +++ b/web-app/src/hooks/useMediaQuery.ts @@ -0,0 +1,90 @@ +import { useEffect, useRef, useState } from 'react' +import { create } from 'zustand' + +export interface UseMediaQueryOptions { + getInitialValueInEffect: boolean +} + +type MediaQueryCallback = (event: { matches: boolean; media: string }) => void + +// Zustand store for small screen state +type SmallScreenState = { + isSmallScreen: boolean + setIsSmallScreen: (isSmall: boolean) => void +} + +export const useSmallScreenStore = create((set) => ({ + isSmallScreen: false, + setIsSmallScreen: (isSmall) => set({ isSmallScreen: isSmall }), +})) + +/** + * Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia + * https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent + * */ +function attachMediaListener( + query: MediaQueryList, + callback: MediaQueryCallback +) { + try { + query.addEventListener('change', callback) + return () => query.removeEventListener('change', callback) + } catch (e) { + console.warn(e) + // eslint-disable @typescript-eslint/no-deprecated + query.addListener(callback) + return () => query.removeListener(callback) + // eslint-enable @typescript-eslint/no-deprecated + } +} + +function getInitialValue(query: string, initialValue?: boolean) { + if (typeof initialValue === 'boolean') { + return initialValue + } + + if (typeof window !== 'undefined' && 'matchMedia' in window) { + return window.matchMedia(query).matches + } + + return false +} + +export function useMediaQuery( + query: string, + initialValue?: boolean, + { getInitialValueInEffect }: UseMediaQueryOptions = { + getInitialValueInEffect: true, + } +): boolean { + const [matches, setMatches] = useState( + getInitialValueInEffect ? initialValue : getInitialValue(query) + ) + const queryRef = useRef(null) + + useEffect(() => { + if ('matchMedia' in window) { + queryRef.current = window.matchMedia(query) + setMatches(queryRef.current.matches) + return attachMediaListener(queryRef.current, (event) => + setMatches(event.matches) + ) + } + + return undefined + }, [query]) + + return matches || false +} + +// Specific hook for small screen detection with state management +export const useSmallScreen = (): boolean => { + const { isSmallScreen, setIsSmallScreen } = useSmallScreenStore() + const mediaQuery = useMediaQuery('(max-width: 768px)') + + useEffect(() => { + setIsSmallScreen(mediaQuery) + }, [mediaQuery, setIsSmallScreen]) + + return isSmallScreen +} diff --git a/web-app/src/locales/en/hub.json b/web-app/src/locales/en/hub.json index bdc83bd50..e082c05b5 100644 --- a/web-app/src/locales/en/hub.json +++ b/web-app/src/locales/en/hub.json @@ -13,7 +13,6 @@ "useModel": "Use this model", "downloadModel": "Download model", "searchPlaceholder": "Search for models on Hugging Face...", - "editTheme": "Edit Theme", "joyride": { "recommendedModelTitle": "Recommended Model", "recommendedModelContent": "Browse and download powerful AI models from various providers, all in one place. We suggest starting with Jan-Nano - a model optimized for function calling, tool integration, and research capabilities. It's ideal for building interactive AI agents.", @@ -28,4 +27,4 @@ "next": "Next", "skip": "Skip" } -} \ No newline at end of file +} diff --git a/web-app/src/locales/id/common.json b/web-app/src/locales/id/common.json index 8aa327253..5ba59011b 100644 --- a/web-app/src/locales/id/common.json +++ b/web-app/src/locales/id/common.json @@ -256,12 +256,5 @@ "description": "Cortex gagal dimulai. Silakan periksa log untuk detail lebih lanjut.", "contactSupport": "Hubungi Dukungan", "restartJan": "Restart Jan" - }, - "outOfContextError": { - "title": "Kesalahan di luar konteks", - "description": "Obrolan ini mencapai batas memori AI, seperti papan tulis yang penuh. Kami dapat memperluas jendela memori (disebut ukuran konteks) sehingga mengingat lebih banyak, tetapi mungkin menggunakan lebih banyak memori komputer Anda. Kami juga dapat memotong input, yang berarti akan melupakan sebagian riwayat obrolan untuk memberi ruang bagi pesan baru.", - "increaseContextSizeDescription": "Apakah Anda ingin meningkatkan ukuran konteks?", - "truncateInput": "Potong Input", - "increaseContextSize": "Tingkatkan Ukuran Konteks" } } diff --git a/web-app/src/locales/id/hub.json b/web-app/src/locales/id/hub.json index 7d9fe733f..5aa1e7d1c 100644 --- a/web-app/src/locales/id/hub.json +++ b/web-app/src/locales/id/hub.json @@ -13,7 +13,6 @@ "useModel": "Gunakan model ini", "downloadModel": "Unduh model", "searchPlaceholder": "Cari model di Hugging Face...", - "editTheme": "Edit Tema", "joyride": { "recommendedModelTitle": "Model yang Direkomendasikan", "recommendedModelContent": "Jelajahi dan unduh model AI yang kuat dari berbagai penyedia, semuanya di satu tempat. Kami sarankan memulai dengan Jan-Nano - model yang dioptimalkan untuk pemanggilan fungsi, integrasi alat, dan kemampuan penelitian. Ini ideal untuk membangun agen AI interaktif.", @@ -28,4 +27,4 @@ "next": "Berikutnya", "skip": "Lewati" } -} \ No newline at end of file +} diff --git a/web-app/src/locales/vn/common.json b/web-app/src/locales/vn/common.json index f970b9b4d..654b2814c 100644 --- a/web-app/src/locales/vn/common.json +++ b/web-app/src/locales/vn/common.json @@ -256,12 +256,5 @@ "description": "Cortex không khởi động được. Vui lòng kiểm tra log để biết thêm chi tiết.", "contactSupport": "Liên hệ Hỗ trợ", "restartJan": "Khởi động lại Jan" - }, - "outOfContextError": { - "title": "Lỗi ngoài ngữ cảnh", - "description": "Cuộc trò chuyện này đang đạt đến giới hạn bộ nhớ của AI, giống như một bảng trắng đang đầy. Chúng ta có thể mở rộng cửa sổ bộ nhớ (gọi là kích thước ngữ cảnh) để nó nhớ nhiều hơn, nhưng có thể sử dụng nhiều bộ nhớ máy tính của bạn hơn. Chúng ta cũng có thể cắt bớt đầu vào, có nghĩa là nó sẽ quên một phần lịch sử trò chuyện để nhường chỗ cho tin nhắn mới.", - "increaseContextSizeDescription": "Bạn có muốn tăng kích thước ngữ cảnh không?", - "truncateInput": "Cắt bớt Đầu vào", - "increaseContextSize": "Tăng Kích thước Ngữ cảnh" } } diff --git a/web-app/src/locales/vn/hub.json b/web-app/src/locales/vn/hub.json index 34f6b485b..8b38d84cc 100644 --- a/web-app/src/locales/vn/hub.json +++ b/web-app/src/locales/vn/hub.json @@ -13,7 +13,6 @@ "useModel": "Sử dụng mô hình này", "downloadModel": "Tải xuống mô hình", "searchPlaceholder": "Tìm kiếm các mô hình trên Hugging Face...", - "editTheme": "Chỉnh sửa chủ đề", "joyride": { "recommendedModelTitle": "Mô hình được đề xuất", "recommendedModelContent": "Duyệt và tải xuống các mô hình AI mạnh mẽ từ nhiều nhà cung cấp khác nhau, tất cả ở cùng một nơi. Chúng tôi khuyên bạn nên bắt đầu với Jan-Nano - một mô hình được tối ưu hóa cho các khả năng gọi hàm, tích hợp công cụ và nghiên cứu. Nó lý tưởng để xây dựng các tác nhân AI tương tác.", @@ -28,4 +27,4 @@ "next": "Tiếp theo", "skip": "Bỏ qua" } -} \ No newline at end of file +} diff --git a/web-app/src/locales/zh-CN/common.json b/web-app/src/locales/zh-CN/common.json index 67a3f002a..40e392c97 100644 --- a/web-app/src/locales/zh-CN/common.json +++ b/web-app/src/locales/zh-CN/common.json @@ -256,12 +256,5 @@ "description": "Cortex 启动失败。请检查日志以获取更多详细信息。", "contactSupport": "联系支持", "restartJan": "重启 Jan" - }, - "outOfContextError": { - "title": "超出上下文错误", - "description": "此聊天正在达到AI的内存限制,就像白板填满了一样。我们可以扩展内存窗口(称为上下文大小),使其记住更多内容,但可能会使用更多计算机内存。我们也可以截断输入,这意味着它会忘记一些聊天历史记录,为新消息腾出空间。", - "increaseContextSizeDescription": "您想要增加上下文大小吗?", - "truncateInput": "截断输入", - "increaseContextSize": "增加上下文大小" } } diff --git a/web-app/src/locales/zh-CN/hub.json b/web-app/src/locales/zh-CN/hub.json index 39231c2fb..dc005611a 100644 --- a/web-app/src/locales/zh-CN/hub.json +++ b/web-app/src/locales/zh-CN/hub.json @@ -13,7 +13,6 @@ "useModel": "使用此模型", "downloadModel": "下载模型", "searchPlaceholder": "在 Hugging Face 上搜索模型...", - "editTheme": "编辑主题", "joyride": { "recommendedModelTitle": "推荐模型", "recommendedModelContent": "在一个地方浏览和下载来自不同提供商的强大 AI 模型。我们建议从 Jan-Nano 开始 - 这是一个针对函数调用、工具集成和研究功能进行优化的模型。它非常适合构建交互式 AI 代理。", @@ -28,4 +27,4 @@ "next": "下一步", "skip": "跳过" } -} \ No newline at end of file +} diff --git a/web-app/src/locales/zh-TW/common.json b/web-app/src/locales/zh-TW/common.json index 04ae212da..92b4a3c0d 100644 --- a/web-app/src/locales/zh-TW/common.json +++ b/web-app/src/locales/zh-TW/common.json @@ -256,12 +256,5 @@ "description": "Cortex 啟動失敗。請檢查日誌以獲取更多詳細信息。", "contactSupport": "聯繫支援", "restartJan": "重啟 Jan" - }, - "outOfContextError": { - "title": "超出上下文錯誤", - "description": "此聊天正在達到AI的記憶體限制,就像白板填滿了一樣。我們可以擴展記憶體視窗(稱為上下文大小),使其記住更多內容,但可能會使用更多電腦記憶體。我們也可以截斷輸入,這意味著它會忘記一些聊天歷史記錄,為新訊息騰出空間。", - "increaseContextSizeDescription": "您想要增加上下文大小嗎?", - "truncateInput": "截斷輸入", - "increaseContextSize": "增加上下文大小" } } diff --git a/web-app/src/locales/zh-TW/hub.json b/web-app/src/locales/zh-TW/hub.json index e4a4df0d5..f35a4485a 100644 --- a/web-app/src/locales/zh-TW/hub.json +++ b/web-app/src/locales/zh-TW/hub.json @@ -13,7 +13,6 @@ "useModel": "使用此模型", "downloadModel": "下載模型", "searchPlaceholder": "在 Hugging Face 上搜尋模型...", - "editTheme": "編輯主題", "joyride": { "recommendedModelTitle": "推薦模型", "recommendedModelContent": "在一個地方瀏覽和下載來自不同提供商的強大 AI 模型。我們建議從 Jan-Nano 開始 - 這是一個針對函數調用、工具整合和研究功能進行優化的模型。它非常適合構建互動式 AI 代理。", @@ -28,4 +27,4 @@ "next": "下一步", "skip": "略過" } -} \ No newline at end of file +} diff --git a/web-app/src/routeTree.gen.ts b/web-app/src/routeTree.gen.ts index 52782cb2e..bbd3db391 100644 --- a/web-app/src/routeTree.gen.ts +++ b/web-app/src/routeTree.gen.ts @@ -27,6 +27,7 @@ import { Route as SettingsGeneralImport } from './routes/settings/general' import { Route as SettingsExtensionsImport } from './routes/settings/extensions' import { Route as SettingsAppearanceImport } from './routes/settings/appearance' import { Route as LocalApiServerLogsImport } from './routes/local-api-server/logs' +import { Route as SettingsProvidersIndexImport } from './routes/settings/providers/index' import { Route as SettingsProvidersProviderNameImport } from './routes/settings/providers/$providerName' // Create/Update Routes @@ -127,6 +128,12 @@ const LocalApiServerLogsRoute = LocalApiServerLogsImport.update({ getParentRoute: () => rootRoute, } as any) +const SettingsProvidersIndexRoute = SettingsProvidersIndexImport.update({ + id: '/settings/providers/', + path: '/settings/providers/', + getParentRoute: () => rootRoute, +} as any) + const SettingsProvidersProviderNameRoute = SettingsProvidersProviderNameImport.update({ id: '/settings/providers/$providerName', @@ -257,6 +264,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SettingsProvidersProviderNameImport parentRoute: typeof rootRoute } + '/settings/providers/': { + id: '/settings/providers/' + path: '/settings/providers' + fullPath: '/settings/providers' + preLoaderRoute: typeof SettingsProvidersIndexImport + parentRoute: typeof rootRoute + } } } @@ -280,6 +294,7 @@ export interface FileRoutesByFullPath { '/settings/shortcuts': typeof SettingsShortcutsRoute '/threads/$threadId': typeof ThreadsThreadIdRoute '/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute + '/settings/providers': typeof SettingsProvidersIndexRoute } export interface FileRoutesByTo { @@ -300,6 +315,7 @@ export interface FileRoutesByTo { '/settings/shortcuts': typeof SettingsShortcutsRoute '/threads/$threadId': typeof ThreadsThreadIdRoute '/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute + '/settings/providers': typeof SettingsProvidersIndexRoute } export interface FileRoutesById { @@ -321,6 +337,7 @@ export interface FileRoutesById { '/settings/shortcuts': typeof SettingsShortcutsRoute '/threads/$threadId': typeof ThreadsThreadIdRoute '/settings/providers/$providerName': typeof SettingsProvidersProviderNameRoute + '/settings/providers/': typeof SettingsProvidersIndexRoute } export interface FileRouteTypes { @@ -343,6 +360,7 @@ export interface FileRouteTypes { | '/settings/shortcuts' | '/threads/$threadId' | '/settings/providers/$providerName' + | '/settings/providers' fileRoutesByTo: FileRoutesByTo to: | '/' @@ -362,6 +380,7 @@ export interface FileRouteTypes { | '/settings/shortcuts' | '/threads/$threadId' | '/settings/providers/$providerName' + | '/settings/providers' id: | '__root__' | '/' @@ -381,6 +400,7 @@ export interface FileRouteTypes { | '/settings/shortcuts' | '/threads/$threadId' | '/settings/providers/$providerName' + | '/settings/providers/' fileRoutesById: FileRoutesById } @@ -402,6 +422,7 @@ export interface RootRouteChildren { SettingsShortcutsRoute: typeof SettingsShortcutsRoute ThreadsThreadIdRoute: typeof ThreadsThreadIdRoute SettingsProvidersProviderNameRoute: typeof SettingsProvidersProviderNameRoute + SettingsProvidersIndexRoute: typeof SettingsProvidersIndexRoute } const rootRouteChildren: RootRouteChildren = { @@ -422,6 +443,7 @@ const rootRouteChildren: RootRouteChildren = { SettingsShortcutsRoute: SettingsShortcutsRoute, ThreadsThreadIdRoute: ThreadsThreadIdRoute, SettingsProvidersProviderNameRoute: SettingsProvidersProviderNameRoute, + SettingsProvidersIndexRoute: SettingsProvidersIndexRoute, } export const routeTree = rootRoute @@ -450,7 +472,8 @@ export const routeTree = rootRoute "/settings/privacy", "/settings/shortcuts", "/threads/$threadId", - "/settings/providers/$providerName" + "/settings/providers/$providerName", + "/settings/providers/" ] }, "/": { @@ -503,6 +526,9 @@ export const routeTree = rootRoute }, "/settings/providers/$providerName": { "filePath": "settings/providers/$providerName.tsx" + }, + "/settings/providers/": { + "filePath": "settings/providers/index.tsx" } } } diff --git a/web-app/src/routes/__root.tsx b/web-app/src/routes/__root.tsx index dc94a86e1..d6383f394 100644 --- a/web-app/src/routes/__root.tsx +++ b/web-app/src/routes/__root.tsx @@ -44,8 +44,8 @@ const AppLayout = () => { {/* Main content panel */}
diff --git a/web-app/src/routes/assistant.tsx b/web-app/src/routes/assistant.tsx index 031003766..fe0cf6fe7 100644 --- a/web-app/src/routes/assistant.tsx +++ b/web-app/src/routes/assistant.tsx @@ -62,57 +62,60 @@ function Assistant() { {t('assistants:title')}
-
- {assistants.map((assistant) => ( -
-
-

-
- {assistant?.avatar && ( - - - - )} - {assistant.name} -
-

-
-
{ - setEditingKey(assistant.id) - setOpen(true) - }} - > - -
-
handleDelete(assistant.id)} - > - +
+ {assistants + .slice().sort((a, b) => a.created_at - b.created_at) + .map((assistant) => ( +
+
+

+
+ {assistant?.avatar && ( + + + + )} + {assistant.name} +
+

+
+
{ + setEditingKey(assistant.id) + setOpen(true) + }} + > + +
+
handleDelete(assistant.id)} + > + +
+

+ {assistant.description} +

-

- {assistant.description} -

-
- ))} + ))} +
{ setEditingKey(null) diff --git a/web-app/src/routes/hub.tsx b/web-app/src/routes/hub.tsx index c0e12130a..8fbb50722 100644 --- a/web-app/src/routes/hub.tsx +++ b/web-app/src/routes/hub.tsx @@ -259,11 +259,16 @@ function Hub() {
)} {isDownloaded ? ( - ) : (
} - description={ + descriptionOutside={
{t('mcp-servers:command')}: {config.command} diff --git a/web-app/src/routes/settings/providers/$providerName.tsx b/web-app/src/routes/settings/providers/$providerName.tsx index 1785f7b85..d15260908 100644 --- a/web-app/src/routes/settings/providers/$providerName.tsx +++ b/web-app/src/routes/settings/providers/$providerName.tsx @@ -1,9 +1,8 @@ import { Card, CardItem } from '@/containers/Card' import HeaderPage from '@/containers/HeaderPage' -import ProvidersMenu from '@/containers/ProvidersMenu' +import SettingsMenu from '@/containers/SettingsMenu' import { useModelProvider } from '@/hooks/useModelProvider' import { cn, getProviderTitle } from '@/lib/utils' -import { Switch } from '@/components/ui/switch' import { open } from '@tauri-apps/plugin-dialog' import { getActiveModels, @@ -212,6 +211,7 @@ function ProviderDetail() { showSkipButton={true} hideCloseButton={true} spotlightClicks={true} + disableOverlay={IS_LINUX} disableOverlayClose={true} callback={handleJoyrideCallback} locale={{ @@ -227,23 +227,13 @@ function ProviderDetail() {

{t('common:settings')}

-
- -
+

{getProviderTitle(providerName)}

- { - if (provider) { - updateProvider(providerName, { ...provider, active: e }) - } - }} - />
-

{model.id}

+

+ {model.id} +

} diff --git a/web-app/src/routes/settings/providers/index.tsx b/web-app/src/routes/settings/providers/index.tsx new file mode 100644 index 000000000..94c01865b --- /dev/null +++ b/web-app/src/routes/settings/providers/index.tsx @@ -0,0 +1,187 @@ +import { createFileRoute } from '@tanstack/react-router' +import { route } from '@/constants/routes' +import SettingsMenu from '@/containers/SettingsMenu' +import HeaderPage from '@/containers/HeaderPage' +import { Button } from '@/components/ui/button' +import { Card, CardItem } from '@/containers/Card' +import { useTranslation } from '@/i18n/react-i18next-compat' +import { useModelProvider } from '@/hooks/useModelProvider' +import { useNavigate } from '@tanstack/react-router' +import { IconCirclePlus, IconSettings } from '@tabler/icons-react' +import { getProviderTitle } from '@/lib/utils' +import ProvidersAvatar from '@/containers/ProvidersAvatar' +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Switch } from '@/components/ui/switch' +import { useCallback, useState } from 'react' +import { openAIProviderSettings } from '@/mock/data' +import cloneDeep from 'lodash/cloneDeep' +import { toast } from 'sonner' + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const Route = createFileRoute(route.settings.model_providers as any)({ + component: ModelProviders, +}) + +function ModelProviders() { + const { t } = useTranslation() + const { providers, addProvider, updateProvider } = useModelProvider() + const navigate = useNavigate() + const [name, setName] = useState('') + + const createProvider = useCallback(() => { + if (providers.some((e) => e.provider === name)) { + toast.error(t('providerAlreadyExists', { name })) + return + } + const newProvider = { + provider: name, + active: true, + models: [], + settings: cloneDeep(openAIProviderSettings) as ProviderSetting[], + api_key: '', + base_url: 'https://api.openai.com/v1', + } + addProvider(newProvider) + setTimeout(() => { + navigate({ + to: route.settings.providers, + params: { + providerName: name, + }, + }) + }, 0) + }, [providers, name, addProvider, t, navigate]) + + return ( +
+ +

{t('common:settings')}

+
+
+ +
+
+ {/* Model Providers */} + + + {t('common:modelProviders')} + + + + + + + + + {t('provider:addOpenAIProvider')} + + setName(e.target.value)} + className="mt-2" + placeholder={t('provider:enterNameForProvider')} + onKeyDown={(e) => { + // Prevent key from being captured by parent components + e.stopPropagation() + }} + /> + + + + + + + + + + + +
+ } + > + {providers.map((provider, index) => ( + + +
+

+ {getProviderTitle(provider.provider)} +

+

+ {provider.models.length} Models +

+
+
+ } + actions={ +
+ {provider.active && ( + + )} + { + updateProvider(provider.provider, { + ...provider, + active: e, + }) + }} + /> +
+ } + /> + ))} + +
+
+
+
+ ) +} diff --git a/web-app/src/routes/threads/$threadId.tsx b/web-app/src/routes/threads/$threadId.tsx index ddc6f5769..38ad8927d 100644 --- a/web-app/src/routes/threads/$threadId.tsx +++ b/web-app/src/routes/threads/$threadId.tsx @@ -19,6 +19,7 @@ import DropdownAssistant from '@/containers/DropdownAssistant' import { useAssistant } from '@/hooks/useAssistant' import { useAppearance } from '@/hooks/useAppearance' import { useTranslation } from '@/i18n/react-i18next-compat' +import { useSmallScreen } from '@/hooks/useMediaQuery' // as route.threadsDetail export const Route = createFileRoute('/threads/$threadId')({ @@ -37,6 +38,7 @@ function ThreadDetail() { const { setMessages } = useMessages() const { streamingContent } = useAppState() const { appMainViewBgColor, chatWidth } = useAppearance() + const isSmallScreen = useSmallScreen() const { messages } = useMessages( useShallow((state) => ({ @@ -213,7 +215,8 @@ function ThreadDetail() {
{messages && @@ -223,7 +226,7 @@ function ThreadDetail() { return (
@@ -244,13 +247,17 @@ function ThreadDetail() {
) })} - +
, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -10724,6 +11497,22 @@ __metadata: languageName: node linkType: hard +"glob@npm:^11.0.0": + version: 11.0.3 + resolution: "glob@npm:11.0.3" + dependencies: + foreground-child: "npm:^3.3.1" + jackspeak: "npm:^4.1.1" + minimatch: "npm:^10.0.3" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^2.0.0" + bin: + glob: dist/esm/bin.mjs + checksum: 10c0/7d24457549ec2903920dfa3d8e76850e7c02aa709122f0164b240c712f5455c0b457e6f2a1eee39344c6148e39895be8094ae8cfef7ccc3296ed30bce250c661 + languageName: node + linkType: hard + "glob@npm:^7.0.5, glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -10863,7 +11652,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -11516,6 +12305,13 @@ __metadata: languageName: node linkType: hard +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10c0/f8ba7ede69bee9260241ad078d2d535848745ff5f6995c7c7cb41cfdc9ccc213f66e10fa5afb881f90298b24a3f7344b637b592beb4f54e582770cdce3f1f039 + languageName: node + linkType: hard + "import-fresh@npm:^3.2.1": version: 3.3.1 resolution: "import-fresh@npm:3.3.1" @@ -11526,7 +12322,7 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.0.2": +"import-local@npm:^3.2.0": version: 3.2.0 resolution: "import-local@npm:3.2.0" dependencies: @@ -11820,7 +12616,7 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.0.0": +"is-generator-fn@npm:^2.1.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" checksum: 10c0/2957cab387997a466cd0bf5c1b6047bd21ecb32bdcfd8996b15747aa01002c1c88731802f1b3d34ac99f4f6874b626418bd118658cf39380fe5fff32a3af9c4d @@ -12140,7 +12936,7 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-instrument@npm:^6.0.0": +"istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": version: 6.0.3 resolution: "istanbul-lib-instrument@npm:6.0.3" dependencies: @@ -12164,14 +12960,14 @@ __metadata: languageName: node linkType: hard -"istanbul-lib-source-maps@npm:^4.0.0": - version: 4.0.1 - resolution: "istanbul-lib-source-maps@npm:4.0.1" +"istanbul-lib-source-maps@npm:^5.0.0": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" debug: "npm:^4.1.1" istanbul-lib-coverage: "npm:^3.0.0" - source-map: "npm:^0.6.1" - checksum: 10c0/19e4cc405016f2c906dff271a76715b3e881fa9faeb3f09a86cb99b8512b3a5ed19cadfe0b54c17ca0e54c1142c9c6de9330d65506e35873994e06634eebeb66 + checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f languageName: node linkType: hard @@ -12208,6 +13004,15 @@ __metadata: languageName: node linkType: hard +"jackspeak@npm:^4.1.1": + version: 4.1.1 + resolution: "jackspeak@npm:4.1.1" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + checksum: 10c0/84ec4f8e21d6514db24737d9caf65361511f75e5e424980eebca4199f400874f45e562ac20fa8aeb1dd20ca2f3f81f0788b6e9c3e64d216a5794fd6f30e0e042 + languageName: node + linkType: hard + "jake@npm:^10.8.5": version: 10.9.2 resolution: "jake@npm:10.9.2" @@ -12232,7 +13037,7 @@ __metadata: cross-env: "npm:^7.0.3" download-cli: "npm:^1.1.1" husky: "npm:^9.1.5" - jest: "npm:^29.7.0" + jest: "npm:^30.0.3" jest-environment-jsdom: "npm:^29.7.0" rimraf: "npm:^3.0.2" run-script-os: "npm:^1.1.6" @@ -12242,106 +13047,122 @@ __metadata: languageName: unknown linkType: soft -"jest-changed-files@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-changed-files@npm:29.7.0" +"jest-changed-files@npm:30.0.2": + version: 30.0.2 + resolution: "jest-changed-files@npm:30.0.2" dependencies: - execa: "npm:^5.0.0" - jest-util: "npm:^29.7.0" + execa: "npm:^5.1.1" + jest-util: "npm:30.0.2" p-limit: "npm:^3.1.0" - checksum: 10c0/e071384d9e2f6bb462231ac53f29bff86f0e12394c1b49ccafbad225ce2ab7da226279a8a94f421949920bef9be7ef574fd86aee22e8adfa149be73554ab828b + checksum: 10c0/794c9e47c460974f2303631d9ee44845d03f4ccd5240649a5f736aa94af78fa5931022324ab302c577dad6adb442ed17140dee9b9985bbfa0d43cad3048a7350 languageName: node linkType: hard -"jest-circus@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-circus@npm:29.7.0" +"jest-circus@npm:30.0.3": + version: 30.0.3 + resolution: "jest-circus@npm:30.0.3" dependencies: - "@jest/environment": "npm:^29.7.0" - "@jest/expect": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" + "@jest/environment": "npm:30.0.2" + "@jest/expect": "npm:30.0.3" + "@jest/test-result": "npm:30.0.2" + "@jest/types": "npm:30.0.1" "@types/node": "npm:*" - chalk: "npm:^4.0.0" + chalk: "npm:^4.1.2" co: "npm:^4.6.0" - dedent: "npm:^1.0.0" - is-generator-fn: "npm:^2.0.0" - jest-each: "npm:^29.7.0" - jest-matcher-utils: "npm:^29.7.0" - jest-message-util: "npm:^29.7.0" - jest-runtime: "npm:^29.7.0" - jest-snapshot: "npm:^29.7.0" - jest-util: "npm:^29.7.0" + dedent: "npm:^1.6.0" + is-generator-fn: "npm:^2.1.0" + jest-each: "npm:30.0.2" + jest-matcher-utils: "npm:30.0.3" + jest-message-util: "npm:30.0.2" + jest-runtime: "npm:30.0.3" + jest-snapshot: "npm:30.0.3" + jest-util: "npm:30.0.2" p-limit: "npm:^3.1.0" - pretty-format: "npm:^29.7.0" - pure-rand: "npm:^6.0.0" + pretty-format: "npm:30.0.2" + pure-rand: "npm:^7.0.0" slash: "npm:^3.0.0" - stack-utils: "npm:^2.0.3" - checksum: 10c0/8d15344cf7a9f14e926f0deed64ed190c7a4fa1ed1acfcd81e4cc094d3cc5bf7902ebb7b874edc98ada4185688f90c91e1747e0dfd7ac12463b097968ae74b5e + stack-utils: "npm:^2.0.6" + checksum: 10c0/cb0838cc9f08984614d92c5fe857ea95f1bdff6de4a510a1b228cc9c0513d18bb2db89dcaf55624e754b11d77fb77bdba1fc56c6af34c1534102c498ce058399 languageName: node linkType: hard -"jest-cli@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-cli@npm:29.7.0" +"jest-cli@npm:30.0.3": + version: 30.0.3 + resolution: "jest-cli@npm:30.0.3" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/test-result": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - create-jest: "npm:^29.7.0" - exit: "npm:^0.1.2" - import-local: "npm:^3.0.2" - jest-config: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - yargs: "npm:^17.3.1" + "@jest/core": "npm:30.0.3" + "@jest/test-result": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + chalk: "npm:^4.1.2" + exit-x: "npm:^0.2.2" + import-local: "npm:^3.2.0" + jest-config: "npm:30.0.3" + jest-util: "npm:30.0.2" + jest-validate: "npm:30.0.2" + yargs: "npm:^17.7.2" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10c0/a658fd55050d4075d65c1066364595962ead7661711495cfa1dfeecf3d6d0a8ffec532f3dbd8afbb3e172dd5fd2fb2e813c5e10256e7cf2fea766314942fb43a + jest: ./bin/jest.js + checksum: 10c0/17925e9e885b00069e06672c221fbe073d1bff1d869f228bcba08ac23bf8d2c258c7211ce4d0e8408ca7d0edf0afb8ae4098e3d0f5da253eed22d385b135ca90 languageName: node linkType: hard -"jest-config@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-config@npm:29.7.0" +"jest-config@npm:30.0.3": + version: 30.0.3 + resolution: "jest-config@npm:30.0.3" dependencies: - "@babel/core": "npm:^7.11.6" - "@jest/test-sequencer": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - babel-jest: "npm:^29.7.0" - chalk: "npm:^4.0.0" - ci-info: "npm:^3.2.0" - deepmerge: "npm:^4.2.2" - glob: "npm:^7.1.3" - graceful-fs: "npm:^4.2.9" - jest-circus: "npm:^29.7.0" - jest-environment-node: "npm:^29.7.0" - jest-get-type: "npm:^29.6.3" - jest-regex-util: "npm:^29.6.3" - jest-resolve: "npm:^29.7.0" - jest-runner: "npm:^29.7.0" - jest-util: "npm:^29.7.0" - jest-validate: "npm:^29.7.0" - micromatch: "npm:^4.0.4" + "@babel/core": "npm:^7.27.4" + "@jest/get-type": "npm:30.0.1" + "@jest/pattern": "npm:30.0.1" + "@jest/test-sequencer": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + babel-jest: "npm:30.0.2" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + deepmerge: "npm:^4.3.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-circus: "npm:30.0.3" + jest-docblock: "npm:30.0.1" + jest-environment-node: "npm:30.0.2" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.0.2" + jest-runner: "npm:30.0.3" + jest-util: "npm:30.0.2" + jest-validate: "npm:30.0.2" + micromatch: "npm:^4.0.8" parse-json: "npm:^5.2.0" - pretty-format: "npm:^29.7.0" + pretty-format: "npm:30.0.2" slash: "npm:^3.0.0" strip-json-comments: "npm:^3.1.1" peerDependencies: "@types/node": "*" + esbuild-register: ">=3.4.0" ts-node: ">=9.0.0" peerDependenciesMeta: "@types/node": optional: true + esbuild-register: + optional: true ts-node: optional: true - checksum: 10c0/bab23c2eda1fff06e0d104b00d6adfb1d1aabb7128441899c9bff2247bd26710b050a5364281ce8d52b46b499153bf7e3ee88b19831a8f3451f1477a0246a0f1 + checksum: 10c0/bcde9e0e715bbc12dd36a135d6e081566291b0726ed7b3ac9a1e2ee2ade7c9bcc25d312ef8a649b72b9c99e2ad6661eb843eeb919ba6206f2ec2acccdd1e57d2 + languageName: node + linkType: hard + +"jest-diff@npm:30.0.3": + version: 30.0.3 + resolution: "jest-diff@npm:30.0.3" + dependencies: + "@jest/diff-sequences": "npm:30.0.1" + "@jest/get-type": "npm:30.0.1" + chalk: "npm:^4.1.2" + pretty-format: "npm:30.0.2" + checksum: 10c0/f6aaed30fc99bdca4b8b4505b283ffc78b780aa1bf33670dfbfe439e124721e7f6198c03217f7ed17a22c7d2ca79363afd6a4245643596fa21ae082b6b4ed4f5 languageName: node linkType: hard @@ -12357,6 +13178,15 @@ __metadata: languageName: node linkType: hard +"jest-docblock@npm:30.0.1": + version: 30.0.1 + resolution: "jest-docblock@npm:30.0.1" + dependencies: + detect-newline: "npm:^3.1.0" + checksum: 10c0/f9bad2651db8afa029867ea7a40f422c9d73c67657360297371846a314a40c8786424be00483261df9137499f52c2af28cd458fbd15a7bf7fac8775b4bcd6ee1 + languageName: node + linkType: hard + "jest-docblock@npm:^29.7.0": version: 29.7.0 resolution: "jest-docblock@npm:29.7.0" @@ -12366,16 +13196,16 @@ __metadata: languageName: node linkType: hard -"jest-each@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-each@npm:29.7.0" +"jest-each@npm:30.0.2": + version: 30.0.2 + resolution: "jest-each@npm:30.0.2" dependencies: - "@jest/types": "npm:^29.6.3" - chalk: "npm:^4.0.0" - jest-get-type: "npm:^29.6.3" - jest-util: "npm:^29.7.0" - pretty-format: "npm:^29.7.0" - checksum: 10c0/f7f9a90ebee80cc688e825feceb2613627826ac41ea76a366fa58e669c3b2403d364c7c0a74d862d469b103c843154f8456d3b1c02b487509a12afa8b59edbb4 + "@jest/get-type": "npm:30.0.1" + "@jest/types": "npm:30.0.1" + chalk: "npm:^4.1.2" + jest-util: "npm:30.0.2" + pretty-format: "npm:30.0.2" + checksum: 10c0/6fff0a470d08ba3f0149c58266b7e938e3e183398f99065fe937290f1297ca254635f0f4bca6196514f756fac0a9759144b1c7f67bef97cc0b7fa0b96304df9e languageName: node linkType: hard @@ -12400,6 +13230,21 @@ __metadata: languageName: node linkType: hard +"jest-environment-node@npm:30.0.2": + version: 30.0.2 + resolution: "jest-environment-node@npm:30.0.2" + dependencies: + "@jest/environment": "npm:30.0.2" + "@jest/fake-timers": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + jest-mock: "npm:30.0.2" + jest-util: "npm:30.0.2" + jest-validate: "npm:30.0.2" + checksum: 10c0/e58515d26f13704c3be6281d029c4fa0902172d2a55751205badf0153630520c4e651f7923577e1ab0dfbb64c4fedb1e4b78622b53b3a8d8e0515c1923f3adc3 + languageName: node + linkType: hard + "jest-environment-node@npm:^29.7.0": version: 29.7.0 resolution: "jest-environment-node@npm:29.7.0" @@ -12421,6 +13266,28 @@ __metadata: languageName: node linkType: hard +"jest-haste-map@npm:30.0.2": + version: 30.0.2 + resolution: "jest-haste-map@npm:30.0.2" + dependencies: + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + anymatch: "npm:^3.1.3" + fb-watchman: "npm:^2.0.2" + fsevents: "npm:^2.3.3" + graceful-fs: "npm:^4.2.11" + jest-regex-util: "npm:30.0.1" + jest-util: "npm:30.0.2" + jest-worker: "npm:30.0.2" + micromatch: "npm:^4.0.8" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10c0/6427b6976beb3fd33cae9a516e24f409d0cc0be2afa12a62e95671001a0d0d61662e8b2185027639b2036fe3e3b055e9d9b4dfd2063e787cf2a5d2140da0b80a + languageName: node + linkType: hard + "jest-haste-map@npm:^29.7.0": version: 29.7.0 resolution: "jest-haste-map@npm:29.7.0" @@ -12456,6 +13323,16 @@ __metadata: languageName: node linkType: hard +"jest-leak-detector@npm:30.0.2": + version: 30.0.2 + resolution: "jest-leak-detector@npm:30.0.2" + dependencies: + "@jest/get-type": "npm:30.0.1" + pretty-format: "npm:30.0.2" + checksum: 10c0/1df28475c40b41024adc6e18af0d3dc8d8d318fdbbf5c3560321fea0af2e0784c57f788b5b152efd83274ab6ea8dc3b36662060a83a2a555ffd8cdf7d628ee76 + languageName: node + linkType: hard + "jest-leak-detector@npm:^29.7.0": version: 29.7.0 resolution: "jest-leak-detector@npm:29.7.0" @@ -12466,6 +13343,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:30.0.3": + version: 30.0.3 + resolution: "jest-matcher-utils@npm:30.0.3" + dependencies: + "@jest/get-type": "npm:30.0.1" + chalk: "npm:^4.1.2" + jest-diff: "npm:30.0.3" + pretty-format: "npm:30.0.2" + checksum: 10c0/4d354f6d8d3992228ba5f0ecc728ec0c46f3693805927253d67e461e754deadc1e1b48ae80918e3f029c22da4abed9aaadb5049da1a1697f6714b0f6076eeafa + languageName: node + linkType: hard + "jest-matcher-utils@npm:^29.7.0": version: 29.7.0 resolution: "jest-matcher-utils@npm:29.7.0" @@ -12478,6 +13367,23 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:30.0.2": + version: 30.0.2 + resolution: "jest-message-util@npm:30.0.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@jest/types": "npm:30.0.1" + "@types/stack-utils": "npm:^2.0.3" + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + micromatch: "npm:^4.0.8" + pretty-format: "npm:30.0.2" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.6" + checksum: 10c0/c010d5b7d86e735e2fb4c4a220f57004349f488f5d4663240a7e9f2694d01b5228136540d55036777fde4227b5e0b56f08885b7f69395b295cab878357b1aeb1 + languageName: node + linkType: hard + "jest-message-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-message-util@npm:29.7.0" @@ -12495,6 +13401,17 @@ __metadata: languageName: node linkType: hard +"jest-mock@npm:30.0.2": + version: 30.0.2 + resolution: "jest-mock@npm:30.0.2" + dependencies: + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + jest-util: "npm:30.0.2" + checksum: 10c0/7728997c1d654475b88e18b7ba33a2a1b9f89ce33a9082bf2d14dcc3e831f372f80c762e481777886a3a04b4489ea5390ecdeb21c4def57fba5b2c77086a3959 + languageName: node + linkType: hard + "jest-mock@npm:^29.7.0": version: 29.7.0 resolution: "jest-mock@npm:29.7.0" @@ -12506,7 +13423,7 @@ __metadata: languageName: node linkType: hard -"jest-pnp-resolver@npm:^1.2.2": +"jest-pnp-resolver@npm:^1.2.2, jest-pnp-resolver@npm:^1.2.3": version: 1.2.3 resolution: "jest-pnp-resolver@npm:1.2.3" peerDependencies: @@ -12518,6 +13435,13 @@ __metadata: languageName: node linkType: hard +"jest-regex-util@npm:30.0.1": + version: 30.0.1 + resolution: "jest-regex-util@npm:30.0.1" + checksum: 10c0/f30c70524ebde2d1012afe5ffa5691d5d00f7d5ba9e43d588f6460ac6fe96f9e620f2f9b36a02d0d3e7e77bc8efb8b3450ae3b80ac53c8be5099e01bf54f6728 + languageName: node + linkType: hard + "jest-regex-util@npm:^29.6.3": version: 29.6.3 resolution: "jest-regex-util@npm:29.6.3" @@ -12525,13 +13449,29 @@ __metadata: languageName: node linkType: hard -"jest-resolve-dependencies@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-resolve-dependencies@npm:29.7.0" +"jest-resolve-dependencies@npm:30.0.3": + version: 30.0.3 + resolution: "jest-resolve-dependencies@npm:30.0.3" dependencies: - jest-regex-util: "npm:^29.6.3" - jest-snapshot: "npm:^29.7.0" - checksum: 10c0/b6e9ad8ae5b6049474118ea6441dfddd385b6d1fc471db0136f7c8fbcfe97137a9665e4f837a9f49f15a29a1deb95a14439b7aec812f3f99d08f228464930f0d + jest-regex-util: "npm:30.0.1" + jest-snapshot: "npm:30.0.3" + checksum: 10c0/5684e62f05d19c5ab97b2b2262075f056bd48745bf25501671d0b9a03f2a0548ab04370b9cec6e97207d57ead54d706a67ef3254729cacb6d6405ef381cdf511 + languageName: node + linkType: hard + +"jest-resolve@npm:30.0.2": + version: 30.0.2 + resolution: "jest-resolve@npm:30.0.2" + dependencies: + chalk: "npm:^4.1.2" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.0.2" + jest-pnp-resolver: "npm:^1.2.3" + jest-util: "npm:30.0.2" + jest-validate: "npm:30.0.2" + slash: "npm:^3.0.0" + unrs-resolver: "npm:^1.7.11" + checksum: 10c0/33ae69455b1206a926bb6f7dd46cd4b6cbf5e095387078873a05dfb693bef419b93897e052ee68026b31b5e5f537fdcfce42f2d31af0ce7e64a8179ed7882b51 languageName: node linkType: hard @@ -12552,6 +13492,36 @@ __metadata: languageName: node linkType: hard +"jest-runner@npm:30.0.3": + version: 30.0.3 + resolution: "jest-runner@npm:30.0.3" + dependencies: + "@jest/console": "npm:30.0.2" + "@jest/environment": "npm:30.0.2" + "@jest/test-result": "npm:30.0.2" + "@jest/transform": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + emittery: "npm:^0.13.1" + exit-x: "npm:^0.2.2" + graceful-fs: "npm:^4.2.11" + jest-docblock: "npm:30.0.1" + jest-environment-node: "npm:30.0.2" + jest-haste-map: "npm:30.0.2" + jest-leak-detector: "npm:30.0.2" + jest-message-util: "npm:30.0.2" + jest-resolve: "npm:30.0.2" + jest-runtime: "npm:30.0.3" + jest-util: "npm:30.0.2" + jest-watcher: "npm:30.0.2" + jest-worker: "npm:30.0.2" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10c0/d139ee4ed4f2d7aeefc8c496efc906960e938beadc22dce6167e7270db4e10260092eace6748a6efb7ee2a40e3bd3ee5d60cbefc2a1e3459826cfde69cdb9195 + languageName: node + linkType: hard + "jest-runner@npm:^29.7.0": version: 29.7.0 resolution: "jest-runner@npm:29.7.0" @@ -12581,6 +13551,36 @@ __metadata: languageName: node linkType: hard +"jest-runtime@npm:30.0.3": + version: 30.0.3 + resolution: "jest-runtime@npm:30.0.3" + dependencies: + "@jest/environment": "npm:30.0.2" + "@jest/fake-timers": "npm:30.0.2" + "@jest/globals": "npm:30.0.3" + "@jest/source-map": "npm:30.0.1" + "@jest/test-result": "npm:30.0.2" + "@jest/transform": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + cjs-module-lexer: "npm:^2.1.0" + collect-v8-coverage: "npm:^1.0.2" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.11" + jest-haste-map: "npm:30.0.2" + jest-message-util: "npm:30.0.2" + jest-mock: "npm:30.0.2" + jest-regex-util: "npm:30.0.1" + jest-resolve: "npm:30.0.2" + jest-snapshot: "npm:30.0.3" + jest-util: "npm:30.0.2" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10c0/01a184b80bf1ae2d6eca280daf37e355b983795e342406de461cf4d45c75ec48a635bf89c08d54fb73f851180e870ef82004fd1f6b335f0329dc07f3bd14a94d + languageName: node + linkType: hard + "jest-runtime@npm:^29.7.0": version: 29.7.0 resolution: "jest-runtime@npm:29.7.0" @@ -12611,6 +13611,35 @@ __metadata: languageName: node linkType: hard +"jest-snapshot@npm:30.0.3": + version: 30.0.3 + resolution: "jest-snapshot@npm:30.0.3" + dependencies: + "@babel/core": "npm:^7.27.4" + "@babel/generator": "npm:^7.27.5" + "@babel/plugin-syntax-jsx": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.27.1" + "@babel/types": "npm:^7.27.3" + "@jest/expect-utils": "npm:30.0.3" + "@jest/get-type": "npm:30.0.1" + "@jest/snapshot-utils": "npm:30.0.1" + "@jest/transform": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + babel-preset-current-node-syntax: "npm:^1.1.0" + chalk: "npm:^4.1.2" + expect: "npm:30.0.3" + graceful-fs: "npm:^4.2.11" + jest-diff: "npm:30.0.3" + jest-matcher-utils: "npm:30.0.3" + jest-message-util: "npm:30.0.2" + jest-util: "npm:30.0.2" + pretty-format: "npm:30.0.2" + semver: "npm:^7.7.2" + synckit: "npm:^0.11.8" + checksum: 10c0/0af682495b79bc0e640edbb03ada06db073a0784d6a9c0bb11e592afa4d0dca63c63ab485f540e8d1bd7674456418906e194e7f0660cc20107423d4fe11b4d6e + languageName: node + linkType: hard + "jest-snapshot@npm:^29.7.0": version: 29.7.0 resolution: "jest-snapshot@npm:29.7.0" @@ -12639,6 +13668,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:30.0.2": + version: 30.0.2 + resolution: "jest-util@npm:30.0.2" + dependencies: + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + chalk: "npm:^4.1.2" + ci-info: "npm:^4.2.0" + graceful-fs: "npm:^4.2.11" + picomatch: "npm:^4.0.2" + checksum: 10c0/07de384790b8e5a5925fba5448fa1475790a5b52271fbf99958c18e468da1af940f8b45e330d87766576cf6c5d1f4f41ce51c976483a5079653d9fcdba8aac8e + languageName: node + linkType: hard + "jest-util@npm:^29.0.0, jest-util@npm:^29.7.0": version: 29.7.0 resolution: "jest-util@npm:29.7.0" @@ -12653,6 +13696,20 @@ __metadata: languageName: node linkType: hard +"jest-validate@npm:30.0.2": + version: 30.0.2 + resolution: "jest-validate@npm:30.0.2" + dependencies: + "@jest/get-type": "npm:30.0.1" + "@jest/types": "npm:30.0.1" + camelcase: "npm:^6.3.0" + chalk: "npm:^4.1.2" + leven: "npm:^3.1.0" + pretty-format: "npm:30.0.2" + checksum: 10c0/9fd1b4f604851187655353eefe8db25db9638dd312d2e29d58868e626d78925edefe94fe2c8eb63305eefd41e5fe7f8aff334e2db9db5aaddeec866f9f6561d8 + languageName: node + linkType: hard + "jest-validate@npm:^29.7.0": version: 29.7.0 resolution: "jest-validate@npm:29.7.0" @@ -12667,6 +13724,22 @@ __metadata: languageName: node linkType: hard +"jest-watcher@npm:30.0.2": + version: 30.0.2 + resolution: "jest-watcher@npm:30.0.2" + dependencies: + "@jest/test-result": "npm:30.0.2" + "@jest/types": "npm:30.0.1" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.3.2" + chalk: "npm:^4.1.2" + emittery: "npm:^0.13.1" + jest-util: "npm:30.0.2" + string-length: "npm:^4.0.2" + checksum: 10c0/7cb09da5feaa6c5558e5149406bde354c3e227ef692b5371efe4d13cf566d42a157c04a55f3a201d191afb7ebc49be84b1ed5a744f46497d9ecccc323d8963f5 + languageName: node + linkType: hard + "jest-watcher@npm:^29.7.0": version: 29.7.0 resolution: "jest-watcher@npm:29.7.0" @@ -12683,6 +13756,19 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:30.0.2": + version: 30.0.2 + resolution: "jest-worker@npm:30.0.2" + dependencies: + "@types/node": "npm:*" + "@ungap/structured-clone": "npm:^1.3.0" + jest-util: "npm:30.0.2" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.1.1" + checksum: 10c0/d7d237e763a2f1aed4eba07f977490442a7bb085f7ab63163afa88776804c2644cc05a1e32da9d05a4b895ad22b2e939ef01a90ffb3024b53fc8c73b8ad1d3f1 + languageName: node + linkType: hard + "jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" @@ -12695,22 +13781,22 @@ __metadata: languageName: node linkType: hard -"jest@npm:^29.7.0": - version: 29.7.0 - resolution: "jest@npm:29.7.0" +"jest@npm:^30.0.3": + version: 30.0.3 + resolution: "jest@npm:30.0.3" dependencies: - "@jest/core": "npm:^29.7.0" - "@jest/types": "npm:^29.6.3" - import-local: "npm:^3.0.2" - jest-cli: "npm:^29.7.0" + "@jest/core": "npm:30.0.3" + "@jest/types": "npm:30.0.1" + import-local: "npm:^3.2.0" + jest-cli: "npm:30.0.3" peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: node-notifier: optional: true bin: - jest: bin/jest.js - checksum: 10c0/f40eb8171cf147c617cc6ada49d062fbb03b4da666cb8d39cdbfb739a7d75eea4c3ca150fb072d0d273dce0c753db4d0467d54906ad0293f59c54f9db4a09d8b + jest: ./bin/jest.js + checksum: 10c0/ae4fbee2756e03b6f99f612438e3b4e25789731599a4d4617ce5002d4c68f169f6223f6b21522fe65cd3d00519e0bb534ac6db6b2cdb7cd46a4ad3ded6542f38 languageName: node linkType: hard @@ -12958,6 +14044,18 @@ __metadata: languageName: node linkType: hard +"jszip@npm:^3.10.1": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: "npm:~3.3.0" + pako: "npm:~1.0.2" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:^1.0.5" + checksum: 10c0/58e01ec9c4960383fb8b38dd5f67b83ccc1ec215bf74c8a5b32f42b6e5fb79fada5176842a11409c4051b5b94275044851814a31076bf49e1be218d3ef57c863 + languageName: node + linkType: hard + "just-diff-apply@npm:^5.2.0": version: 5.5.0 resolution: "just-diff-apply@npm:5.5.0" @@ -13028,13 +14126,6 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^3.0.3": - version: 3.0.3 - resolution: "kleur@npm:3.0.3" - checksum: 10c0/cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b - languageName: node - linkType: hard - "layerr@npm:^3.0.0": version: 3.0.0 resolution: "layerr@npm:3.0.0" @@ -13059,6 +14150,15 @@ __metadata: languageName: node linkType: hard +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10c0/56dd113091978f82f9dc5081769c6f3b947852ecf9feccaf83e14a123bc630c2301439ce6182521e5fbafbde88e88ac38314327a4e0493a1bea7e0699a7af808 + languageName: node + linkType: hard + "lightningcss-darwin-arm64@npm:1.29.2": version: 1.29.2 resolution: "lightningcss-darwin-arm64@npm:1.29.2" @@ -13207,6 +14307,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^7.1.0": + version: 7.2.0 + resolution: "locate-path@npm:7.2.0" + dependencies: + p-locate: "npm:^6.0.0" + checksum: 10c0/139e8a7fe11cfbd7f20db03923cacfa5db9e14fa14887ea121345597472b4a63c1a42a8a5187defeeff6acf98fd568da7382aa39682d38f0af27433953a97751 + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -13235,6 +14344,19 @@ __metadata: languageName: node linkType: hard +"log4js@npm:^6.9.1": + version: 6.9.1 + resolution: "log4js@npm:6.9.1" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + flatted: "npm:^3.2.7" + rfdc: "npm:^1.3.0" + streamroller: "npm:^3.1.5" + checksum: 10c0/05846e48f72d662800c8189bd178c42b4aa2f0c574cfc90a1942cf90b76f621c44019e26796c8fd88da1b6f0fe8272cba607cbaad6ae6ede50a7a096b58197ea + languageName: node + linkType: hard + "longest-streak@npm:^3.0.0": version: 3.1.0 resolution: "longest-streak@npm:3.1.0" @@ -13311,6 +14433,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^11.0.0": + version: 11.1.0 + resolution: "lru-cache@npm:11.1.0" + checksum: 10c0/85c312f7113f65fae6a62de7985348649937eb34fb3d212811acbf6704dc322a421788aca253b62838f1f07049a84cc513d88f494e373d3756514ad263670a64 + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -13320,12 +14449,12 @@ __metadata: languageName: node linkType: hard -"lucide-react@npm:^0.503.0": - version: 0.503.0 - resolution: "lucide-react@npm:0.503.0" +"lucide-react@npm:^0.522.0": + version: 0.522.0 + resolution: "lucide-react@npm:0.522.0" peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/97d282acf1a384da690cd973804d11737ebdba744cd11b2a3807a06d407e0f1f975723d9ce4afcb55058e6bd179d77029272cddcc726232339a3fde263b375b4 + checksum: 10c0/92f18da5ade753c7955a3d0fe3779b62831bf1d6ab15396b6024ef66efe7df7b78e19728e3cf59d1bd01bbee16de0c474a5d6b2741e6b5c97d8374d02f776898 languageName: node linkType: hard @@ -13356,7 +14485,7 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.3.6": +"make-error@npm:^1.1.1, make-error@npm:^1.3.6": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: 10c0/171e458d86854c6b3fc46610cfacf0b45149ba043782558c6875d9f42f222124384ad0b468c92e996d815a8a2003817a710c0a160e49c1c394626f76fa45396f @@ -14198,6 +15327,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^10.0.3": + version: 10.0.3 + resolution: "minimatch@npm:10.0.3" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/e43e4a905c5d70ac4cec8530ceaeccb9c544b1ba8ac45238e2a78121a01c17ff0c373346472d221872563204eabe929ad02669bb575cb1f0cc30facab369f70f + languageName: node + linkType: hard + "minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -14505,6 +15643,15 @@ __metadata: languageName: node linkType: hard +"napi-postinstall@npm:^0.2.4": + version: 0.2.5 + resolution: "napi-postinstall@npm:0.2.5" + bin: + napi-postinstall: lib/cli.js + checksum: 10c0/c4a1a8ca61aece10a6a7b46b834d7689321c4bb164710df9d896a273f24544084c5be95b47c55208036a06ae5bfa0afabb6a8886985d4438543ee07344b9c90c + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -15103,6 +16250,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^4.0.0": + version: 4.0.0 + resolution: "p-limit@npm:4.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10c0/a56af34a77f8df2ff61ddfb29431044557fcbcb7642d5a3233143ebba805fc7306ac1d448de724352861cb99de934bc9ab74f0d16fe6a5460bdbdf938de875ad + languageName: node + linkType: hard + "p-locate@npm:^4.1.0": version: 4.1.0 resolution: "p-locate@npm:4.1.0" @@ -15121,6 +16277,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^6.0.0": + version: 6.0.0 + resolution: "p-locate@npm:6.0.0" + dependencies: + p-limit: "npm:^4.0.0" + checksum: 10c0/d72fa2f41adce59c198270aa4d3c832536c87a1806e0f69dffb7c1a7ca998fb053915ca833d90f166a8c082d3859eabfed95f01698a3214c20df6bb8de046312 + languageName: node + linkType: hard + "p-map@npm:^4.0.0": version: 4.0.0 resolution: "p-map@npm:4.0.0" @@ -15214,7 +16379,7 @@ __metadata: languageName: node linkType: hard -"pako@npm:~1.0.5": +"pako@npm:~1.0.2, pako@npm:~1.0.5": version: 1.0.11 resolution: "pako@npm:1.0.11" checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe @@ -15363,6 +16528,13 @@ __metadata: languageName: node linkType: hard +"path-exists@npm:^5.0.0": + version: 5.0.0 + resolution: "path-exists@npm:5.0.0" + checksum: 10c0/b170f3060b31604cde93eefdb7392b89d832dfbc1bed717c9718cbe0f230c1669b7e75f87e19901da2250b84d092989a0f9e44d2ef41deb09aa3ad28e691a40a + languageName: node + linkType: hard + "path-is-absolute@npm:^1.0.0": version: 1.0.1 resolution: "path-is-absolute@npm:1.0.1" @@ -15394,6 +16566,16 @@ __metadata: languageName: node linkType: hard +"path-scurry@npm:^2.0.0": + version: 2.0.0 + resolution: "path-scurry@npm:2.0.0" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/3da4adedaa8e7ef8d6dc4f35a0ff8f05a9b4d8365f2b28047752b62d4c1ad73eec21e37b1579ef2d075920157856a3b52ae8309c480a6f1a8bbe06ff8e52b33c + languageName: node + linkType: hard + "path-type@npm:^1.0.0": version: 1.1.0 resolution: "path-type@npm:1.1.0" @@ -15512,6 +16694,13 @@ __metadata: languageName: node linkType: hard +"pirates@npm:^4.0.6, pirates@npm:^4.0.7": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + "pkg-dir@npm:^4.2.0": version: 4.2.0 resolution: "pkg-dir@npm:4.2.0" @@ -15530,6 +16719,15 @@ __metadata: languageName: node linkType: hard +"pkg-dir@npm:^7.0.0": + version: 7.0.0 + resolution: "pkg-dir@npm:7.0.0" + dependencies: + find-up: "npm:^6.3.0" + checksum: 10c0/1afb23d2efb1ec9d8b2c4a0c37bf146822ad2774f074cb05b853be5dca1b40815c5960dd126df30ab8908349262a266f31b771e877235870a3b8fd313beebec5 + languageName: node + linkType: hard + "popper.js@npm:^1.16.0": version: 1.16.1 resolution: "popper.js@npm:1.16.1" @@ -15640,7 +16838,18 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": +"pretty-format@npm:30.0.2, pretty-format@npm:^30.0.0": + version: 30.0.2 + resolution: "pretty-format@npm:30.0.2" + dependencies: + "@jest/schemas": "npm:30.0.1" + ansi-styles: "npm:^5.2.0" + react-is: "npm:^18.3.1" + checksum: 10c0/cf542dc2d0be95e2b1c6e3a397a4fc13fce1c9f8feed6b56165c0d23c7a83423abb6b032ed8e3e1b7c1c0709f9b117dd30b5185f107e58f8766616be6de84850 + languageName: node + linkType: hard + +"pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -15738,16 +16947,6 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.0.1": - version: 2.4.2 - resolution: "prompts@npm:2.4.2" - dependencies: - kleur: "npm:^3.0.3" - sisteransi: "npm:^1.0.5" - checksum: 10c0/16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 - languageName: node - linkType: hard - "prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" @@ -15843,10 +17042,10 @@ __metadata: languageName: node linkType: hard -"pure-rand@npm:^6.0.0": - version: 6.1.0 - resolution: "pure-rand@npm:6.1.0" - checksum: 10c0/1abe217897bf74dcb3a0c9aba3555fe975023147b48db540aa2faf507aee91c03bf54f6aef0eb2bf59cc259a16d06b28eca37f0dc426d94f4692aeff02fb0e65 +"pure-rand@npm:^7.0.0": + version: 7.0.1 + resolution: "pure-rand@npm:7.0.1" + checksum: 10c0/9cade41030f5ec95f5d55a11a71404cd6f46b69becaad892097cd7f58e2c6248cd0a933349ca7d21336ab629f1da42ffe899699b671bc4651600eaf6e57f837e languageName: node linkType: hard @@ -16008,7 +17207,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^18.0.0": +"react-is@npm:^18.0.0, react-is@npm:^18.3.1": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10c0/f2f1e60010c683479e74c63f96b09fb41603527cd131a9959e2aee1e5a8b0caf270b365e5ca77d4a6b18aae659b60a86150bb3979073528877029b35aecd2072 @@ -16241,7 +17440,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8": +"readable-stream@npm:^2.0.2, readable-stream@npm:^2.3.0, readable-stream@npm:^2.3.5, readable-stream@npm:^2.3.8, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -16703,6 +17902,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.3.0": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + "rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -16725,6 +17931,18 @@ __metadata: languageName: node linkType: hard +"rimraf@npm:^6.0.1": + version: 6.0.1 + resolution: "rimraf@npm:6.0.1" + dependencies: + glob: "npm:^11.0.0" + package-json-from-dist: "npm:^1.0.0" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10c0/b30b6b072771f0d1e73b4ca5f37bb2944ee09375be9db5f558fcd3310000d29dfcfa93cf7734d75295ad5a7486dc8e40f63089ced1722a664539ffc0c3ece8c6 + languageName: node + linkType: hard + "ripemd160@npm:=2.0.1": version: 2.0.1 resolution: "ripemd160@npm:2.0.1" @@ -17073,6 +18291,18 @@ __metadata: languageName: node linkType: hard +"selenium-webdriver@npm:^4.22.0": + version: 4.34.0 + resolution: "selenium-webdriver@npm:4.34.0" + dependencies: + "@bazel/runfiles": "npm:^6.3.1" + jszip: "npm:^3.10.1" + tmp: "npm:^0.2.3" + ws: "npm:^8.18.2" + checksum: 10c0/bfca387713677284d70b66aa419ad0e6f013945cca1f19bb780a62e4eff1f69190a165e939ef79e8f23af9aa0bd08b9a90c41ee6713eec30b7601034bd5f1824 + languageName: node + linkType: hard + "semver-compare@npm:^1.0.0": version: 1.0.0 resolution: "semver-compare@npm:1.0.0" @@ -17107,6 +18337,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.7.2": + version: 7.7.2 + resolution: "semver@npm:7.7.2" + bin: + semver: bin/semver.js + checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea + languageName: node + linkType: hard + "serialize-error@npm:^7.0.1": version: 7.0.1 resolution: "serialize-error@npm:7.0.1" @@ -17158,7 +18397,7 @@ __metadata: languageName: node linkType: hard -"setimmediate@npm:^1.0.4": +"setimmediate@npm:^1.0.4, setimmediate@npm:^1.0.5": version: 1.0.5 resolution: "setimmediate@npm:1.0.5" checksum: 10c0/5bae81bfdbfbd0ce992893286d49c9693c82b1bcc00dcaaf3a09c8f428fdeacf4190c013598b81875dfac2b08a572422db7df779a99332d0fce186d15a3e4d49 @@ -17262,7 +18501,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 @@ -17297,13 +18536,6 @@ __metadata: languageName: node linkType: hard -"sisteransi@npm:^1.0.5": - version: 1.0.5 - resolution: "sisteransi@npm:1.0.5" - checksum: 10c0/230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 - languageName: node - linkType: hard - "skin-tone@npm:^2.0.0": version: 2.0.0 resolution: "skin-tone@npm:2.0.0" @@ -17363,6 +18595,24 @@ __metadata: languageName: node linkType: hard +"sock-daemon@npm:^1.4.2": + version: 1.4.2 + resolution: "sock-daemon@npm:1.4.2" + dependencies: + rimraf: "npm:^5.0.5" + signal-exit: "npm:^4.1.0" + socket-post-message: "npm:^1.0.3" + checksum: 10c0/1b5e0b02fdd8cd5454fc7de80557c11aac5d88085d0cee80ead08b8f4df5e3c0a4b50ebb2ae2113dab94f36dc88b5d3b7d4b1c2c8e53bbcfbddfc741abf3bd00 + languageName: node + linkType: hard + +"socket-post-message@npm:^1.0.3": + version: 1.0.3 + resolution: "socket-post-message@npm:1.0.3" + checksum: 10c0/d3ffb51dad97754856aaa6709e036196f4b8b674f00366b71591ead122bcdbc073827f67d17c8b03c9a28c921b2c7cb277c581f6ca318d472034eae7afc169d1 + languageName: node + linkType: hard + "socks-proxy-agent@npm:^8.0.3": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" @@ -17467,7 +18717,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.1": +"source-map@npm:^0.6.0, source-map@npm:~0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 10c0/ab55398007c5e5532957cb0beee2368529618ac0ab372d789806f5718123cc4367d57de3904b4e6a4170eb5a0b0f41373066d02ca0735a0c4d75c7d328d3e011 @@ -17584,7 +18834,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.3": +"stack-utils@npm:^2.0.3, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -17639,7 +18889,18 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.1": +"streamroller@npm:^3.1.5": + version: 3.1.5 + resolution: "streamroller@npm:3.1.5" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + fs-extra: "npm:^8.1.0" + checksum: 10c0/0bdeec34ad37487d959ba908f17067c938f544db88b5bb1669497a67a6b676413229ce5a6145c2812d06959ebeb8842e751076647d4b323ca06be612963b9099 + languageName: node + linkType: hard + +"string-length@npm:^4.0.1, string-length@npm:^4.0.2": version: 4.0.2 resolution: "string-length@npm:4.0.2" dependencies: @@ -17851,6 +19112,15 @@ __metadata: languageName: node linkType: hard +"synckit@npm:^0.11.8": + version: 0.11.8 + resolution: "synckit@npm:0.11.8" + dependencies: + "@pkgr/core": "npm:^0.2.4" + checksum: 10c0/a1de5131ee527512afcaafceb2399b2f3e63678e56b831e1cb2dc7019c972a8b654703a3b94ef4166868f87eb984ea252b467c9d9e486b018ec2e6a55c24dfd8 + languageName: node + linkType: hard + "tailwind-merge@npm:^3.2.0": version: 3.2.0 resolution: "tailwind-merge@npm:3.2.0" @@ -17941,6 +19211,21 @@ __metadata: languageName: node linkType: hard +"tests-e2-js@workspace:tests-e2e-js": + version: 0.0.0-use.local + resolution: "tests-e2-js@workspace:tests-e2e-js" + dependencies: + "@tauri-e2e/selenium": "npm:0.2.2" + "@types/node": "npm:^20.14.9" + "@types/selenium-webdriver": "npm:^4.1.28" + log4js: "npm:^6.9.1" + selenium-webdriver: "npm:^4.22.0" + ts-node: "npm:^10.9.2" + tsimp: "npm:^2.0.11" + typescript: "npm:^5.5.2" + languageName: unknown + linkType: soft + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -18047,6 +19332,13 @@ __metadata: languageName: node linkType: hard +"tmp@npm:^0.2.3": + version: 0.2.3 + resolution: "tmp@npm:0.2.3" + checksum: 10c0/3e809d9c2f46817475b452725c2aaa5d11985cf18d32a7a970ff25b568438e2c076c2e8609224feef3b7923fa9749b74428e3e634f6b8e520c534eef2fd24125 + languageName: node + linkType: hard + "tmpl@npm:1.0.5": version: 1.0.5 resolution: "tmpl@npm:1.0.5" @@ -18272,6 +19564,65 @@ __metadata: languageName: node linkType: hard +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10c0/5f29938489f96982a25ba650b64218e83a3357d76f7bede80195c65ab44ad279c8357264639b7abdd5d7e75fc269a83daa0e9c62fd8637a3def67254ecc9ddc2 + languageName: node + linkType: hard + +"tsimp@npm:^2.0.11": + version: 2.0.12 + resolution: "tsimp@npm:2.0.12" + dependencies: + "@isaacs/cached": "npm:^1.0.1" + "@isaacs/catcher": "npm:^1.0.4" + foreground-child: "npm:^3.1.1" + mkdirp: "npm:^3.0.1" + pirates: "npm:^4.0.6" + rimraf: "npm:^6.0.1" + signal-exit: "npm:^4.1.0" + sock-daemon: "npm:^1.4.2" + walk-up-path: "npm:^4.0.0" + peerDependencies: + typescript: ^5.1.0 + bin: + tsimp: dist/esm/bin.mjs + checksum: 10c0/c56c03a6a4df3ab5ebcefcc0b473992cbb7150173c331be6bda01670d5ae3965e65f30c42757cd391100a1c21485e167a05a350d875f41826b35c45008e5fac8 + languageName: node + linkType: hard + "tslib@npm:^1.11.1, tslib@npm:^1.8.1": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -18434,7 +19785,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.8.3, typescript@npm:~5.8.3": +"typescript@npm:^5.5.2, typescript@npm:^5.8.3, typescript@npm:~5.8.3": version: 5.8.3 resolution: "typescript@npm:5.8.3" bin: @@ -18444,7 +19795,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.8.3#optional!builtin, typescript@patch:typescript@npm%3A~5.8.3#optional!builtin": +"typescript@patch:typescript@npm%3A^5.5.2#optional!builtin, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin, typescript@patch:typescript@npm%3A~5.8.3#optional!builtin": version: 5.8.3 resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5" bin: @@ -18682,6 +20033,73 @@ __metadata: languageName: node linkType: hard +"unrs-resolver@npm:^1.7.11": + version: 1.9.2 + resolution: "unrs-resolver@npm:1.9.2" + dependencies: + "@unrs/resolver-binding-android-arm-eabi": "npm:1.9.2" + "@unrs/resolver-binding-android-arm64": "npm:1.9.2" + "@unrs/resolver-binding-darwin-arm64": "npm:1.9.2" + "@unrs/resolver-binding-darwin-x64": "npm:1.9.2" + "@unrs/resolver-binding-freebsd-x64": "npm:1.9.2" + "@unrs/resolver-binding-linux-arm-gnueabihf": "npm:1.9.2" + "@unrs/resolver-binding-linux-arm-musleabihf": "npm:1.9.2" + "@unrs/resolver-binding-linux-arm64-gnu": "npm:1.9.2" + "@unrs/resolver-binding-linux-arm64-musl": "npm:1.9.2" + "@unrs/resolver-binding-linux-ppc64-gnu": "npm:1.9.2" + "@unrs/resolver-binding-linux-riscv64-gnu": "npm:1.9.2" + "@unrs/resolver-binding-linux-riscv64-musl": "npm:1.9.2" + "@unrs/resolver-binding-linux-s390x-gnu": "npm:1.9.2" + "@unrs/resolver-binding-linux-x64-gnu": "npm:1.9.2" + "@unrs/resolver-binding-linux-x64-musl": "npm:1.9.2" + "@unrs/resolver-binding-wasm32-wasi": "npm:1.9.2" + "@unrs/resolver-binding-win32-arm64-msvc": "npm:1.9.2" + "@unrs/resolver-binding-win32-ia32-msvc": "npm:1.9.2" + "@unrs/resolver-binding-win32-x64-msvc": "npm:1.9.2" + napi-postinstall: "npm:^0.2.4" + dependenciesMeta: + "@unrs/resolver-binding-android-arm-eabi": + optional: true + "@unrs/resolver-binding-android-arm64": + optional: true + "@unrs/resolver-binding-darwin-arm64": + optional: true + "@unrs/resolver-binding-darwin-x64": + optional: true + "@unrs/resolver-binding-freebsd-x64": + optional: true + "@unrs/resolver-binding-linux-arm-gnueabihf": + optional: true + "@unrs/resolver-binding-linux-arm-musleabihf": + optional: true + "@unrs/resolver-binding-linux-arm64-gnu": + optional: true + "@unrs/resolver-binding-linux-arm64-musl": + optional: true + "@unrs/resolver-binding-linux-ppc64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-gnu": + optional: true + "@unrs/resolver-binding-linux-riscv64-musl": + optional: true + "@unrs/resolver-binding-linux-s390x-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-gnu": + optional: true + "@unrs/resolver-binding-linux-x64-musl": + optional: true + "@unrs/resolver-binding-wasm32-wasi": + optional: true + "@unrs/resolver-binding-win32-arm64-msvc": + optional: true + "@unrs/resolver-binding-win32-ia32-msvc": + optional: true + "@unrs/resolver-binding-win32-x64-msvc": + optional: true + checksum: 10c0/e3481cc19ea4b25f888e2412bbd80a729b13527a41b035e784b71d1a7d4e2109b58b174adce989085eb75c787435e80ffb385db2b1598288474f53beb01438c0 + languageName: node + linkType: hard + "unset-value@npm:^1.0.0": version: 1.0.0 resolution: "unset-value@npm:1.0.0" @@ -18919,6 +20337,13 @@ __metadata: languageName: node linkType: hard +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10c0/bdc36fb8095d3b41df197f5fb6f11e3a26adf4059df3213e3baa93810d8f0cc76f9a74aaefc18b73e91fe7e19154ed6f134eda6fded2e0f1c8d2272ed2d2d391 + languageName: node + linkType: hard + "v8-to-istanbul@npm:^9.0.1": version: 9.3.0 resolution: "v8-to-istanbul@npm:9.3.0" @@ -19233,6 +20658,13 @@ __metadata: languageName: node linkType: hard +"walk-up-path@npm:^4.0.0": + version: 4.0.0 + resolution: "walk-up-path@npm:4.0.0" + checksum: 10c0/fabe344f91387d1d41df230af962ef18bf703dd4178006d55cd6412caacd187b54440002d4d53a982d4f7f0455567dcffb6d3884533c8b2268928eca3ebd8a19 + languageName: node + linkType: hard + "walker@npm:^1.0.8": version: 1.0.8 resolution: "walker@npm:1.0.8" @@ -19447,7 +20879,7 @@ __metadata: languageName: node linkType: hard -"write-file-atomic@npm:^5.0.0": +"write-file-atomic@npm:^5.0.0, write-file-atomic@npm:^5.0.1": version: 5.0.1 resolution: "write-file-atomic@npm:5.0.1" dependencies: @@ -19472,6 +20904,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.18.2": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + "xml-name-validator@npm:^4.0.0": version: 4.0.0 resolution: "xml-name-validator@npm:4.0.0" @@ -19521,7 +20968,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1, yargs@npm:^17.7.2": +"yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -19546,6 +20993,13 @@ __metadata: languageName: node linkType: hard +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10c0/0732468dd7622ed8a274f640f191f3eaf1f39d5349a1b72836df484998d7d9807fbea094e2f5486d6b0cd2414aad5775972df0e68f8604db89a239f0f4bf7443 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0" @@ -19553,6 +21007,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.2.1 + resolution: "yocto-queue@npm:1.2.1" + checksum: 10c0/5762caa3d0b421f4bdb7a1926b2ae2189fc6e4a14469258f183600028eb16db3e9e0306f46e8ebf5a52ff4b81a881f22637afefbef5399d6ad440824e9b27f9f + languageName: node + linkType: hard + "zod@npm:^3.23.8": version: 3.24.2 resolution: "zod@npm:3.24.2"