diff --git a/.github/scripts/electron-checksum.py b/.github/scripts/electron-checksum.py new file mode 100644 index 000000000..fba4ff609 --- /dev/null +++ b/.github/scripts/electron-checksum.py @@ -0,0 +1,28 @@ +import hashlib +import base64 +import sys + +def hash_file(file_path): + # Create a SHA-512 hash object + sha512 = hashlib.sha512() + + # Read and update the hash object with the content of the file + with open(file_path, 'rb') as f: + while True: + data = f.read(1024 * 1024) # Read in 1 MB chunks + if not data: + break + sha512.update(data) + + # Obtain the hash result and encode it in base64 + hash_base64 = base64.b64encode(sha512.digest()).decode('utf-8') + return hash_base64 + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python3 script.py ") + sys.exit(1) + + file_path = sys.argv[1] + hash_base64_output = hash_file(file_path) + print(hash_base64_output) diff --git a/electron/icons_dev/jan-beta-512x512.png b/.github/scripts/icon-beta.png similarity index 100% rename from electron/icons_dev/jan-beta-512x512.png rename to .github/scripts/icon-beta.png diff --git a/electron/icons_dev/jan-nightly-512x512.png b/.github/scripts/icon-nightly.png similarity index 100% rename from electron/icons_dev/jan-nightly-512x512.png rename to .github/scripts/icon-nightly.png diff --git a/.github/scripts/rename-tauri-app.sh b/.github/scripts/rename-tauri-app.sh new file mode 100644 index 000000000..245528d91 --- /dev/null +++ b/.github/scripts/rename-tauri-app.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +# Check if the correct number of arguments is provided +if [ "$#" -ne 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +INPUT_JSON_FILE="$1" + +CHANNEL="$2" + +if [ "$CHANNEL" == "nightly" ]; then + UPDATER="latest" +else + UPDATER="beta" +fi + +# Check if the input file exists +if [ ! -f "$INPUT_JSON_FILE" ]; then + echo "Input file not found: $INPUT_JSON_FILE" + exit 1 +fi + +# Use jq to transform the content +jq --arg channel "$CHANNEL" --arg updater "$UPDATER" ' + .productName = "Jan-\($channel)" | + .identifier = "jan-\($channel).ai.app" +' "$INPUT_JSON_FILE" > ./tauri.conf.json.tmp + +cat ./tauri.conf.json.tmp + +rm $INPUT_JSON_FILE +mv ./tauri.conf.json.tmp $INPUT_JSON_FILE + +# Update Info.plist if it exists +INFO_PLIST_PATH="./src-tauri/Info.plist" +if [ -f "$INFO_PLIST_PATH" ]; then + echo "Updating Info.plist..." + + # Replace jan.ai.app with jan-{channel}.ai.app + sed -i '' "s|jan\.ai\.app|jan-${CHANNEL}.ai.app|g" "$INFO_PLIST_PATH" + + # Replace jan with jan-{channel} + sed -i '' "s|jan|jan-${CHANNEL}|g" "$INFO_PLIST_PATH" + + echo "Info.plist updated" + + cat ./src-tauri/Info.plist +fi +# Update the layout file +# LAYOUT_FILE_PATH="web/app/layout.tsx" + +# if [ ! -f "$LAYOUT_FILE_PATH" ]; then +# echo "File does not exist: $LAYOUT_FILE_PATH" +# exit 1 +# fi + +# Perform the replacements +# sed -i -e "s#Jan#Jan-$CHANNEL#g" "$LAYOUT_FILE_PATH" + +# Notify completion +# echo "File has been updated: $LAYOUT_FILE_PATH" \ No newline at end of file diff --git a/.github/workflows/jan-electron-build-beta.yml b/.github/workflows/jan-electron-build-beta.yml deleted file mode 100644 index 61ff717ac..000000000 --- a/.github/workflows/jan-electron-build-beta.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Electron Builder - Beta Build - -on: - push: - tags: ["v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+-beta"] - -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 - - build-macos: - uses: ./.github/workflows/template-build-macos.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - new_version: ${{ needs.get-update-version.outputs.new_version }} - beta: true - nightly: false - cortex_api_port: "39271" - - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - new_version: ${{ needs.get-update-version.outputs.new_version }} - beta: true - nightly: false - cortex_api_port: "39271" - - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml - secrets: inherit - needs: [get-update-version] - with: - ref: ${{ github.ref }} - public_provider: github - new_version: ${{ needs.get-update-version.outputs.new_version }} - beta: true - nightly: false - cortex_api_port: "39271" - - sync-temp-to-latest: - needs: [build-macos, build-windows-x64, build-linux-x64] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Getting the repo - uses: actions/checkout@v3 - - name: Sync temp to latest - run: | - # sync temp-beta to beta by copy files that are different or new - aws s3 sync "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/" "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/beta/" - 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-and-update-url-readme: - needs: [build-macos, get-update-version, build-windows-x64, build-linux-x64, sync-temp-to-latest] - runs-on: ubuntu-latest - steps: - - name: Set version to environment variable - run: | - VERSION=${{ needs.get-update-version.outputs.new_version }} - VERSION="${VERSION#v}" - echo "VERSION=$VERSION" >> $GITHUB_ENV - - - name: Notify Discord - uses: Ilshidur/action-discord@master - with: - args: | - Jan-beta App version {{ VERSION }}, has been released, use the following links to download the app with faster speed or visit the Github release page for more information: - - Windows: https://delta.jan.ai/beta/jan-beta-win-x64-{{ VERSION }}.exe - - macOS Universal: https://delta.jan.ai/beta/jan-beta-mac-universal-{{ VERSION }}.dmg - - Linux Deb: https://delta.jan.ai/beta/jan-beta-linux-amd64-{{ VERSION }}.deb - - Linux AppImage: https://delta.jan.ai/beta/jan-beta-linux-x86_64-{{ VERSION }}.AppImage - env: - DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }} \ No newline at end of file diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml index af5bab195..b4d275658 100644 --- a/.github/workflows/jan-electron-build-nightly.yml +++ b/.github/workflows/jan-electron-build-nightly.yml @@ -12,8 +12,9 @@ on: - none - aws-s3 default: none - pull_request_review: - types: [submitted] + pull_request: + branches: + - release/** jobs: set-public-provider: @@ -47,48 +48,84 @@ jobs: get-update-version: uses: ./.github/workflows/template-get-update-version.yml - build-macos: - uses: ./.github/workflows/template-build-macos.yml - needs: [get-update-version, set-public-provider] + 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 }} - nightly: true - beta: false + channel: nightly cortex_api_port: "39261" - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml + 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 }} - nightly: true - beta: false + channel: nightly cortex_api_port: "39261" - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml + + 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 }} - nightly: true - beta: false + channel: nightly cortex_api_port: "39261" sync-temp-to-latest: - needs: [set-public-provider, build-windows-x64, build-linux-x64, build-macos] + 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 }} @@ -97,7 +134,14 @@ jobs: AWS_EC2_METADATA_DISABLED: "true" noti-discord-nightly-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + 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 @@ -108,7 +152,14 @@ jobs: new_version: ${{ needs.get-update-version.outputs.new_version }} noti-discord-pre-release-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + 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 @@ -119,7 +170,14 @@ jobs: new_version: ${{ needs.get-update-version.outputs.new_version }} noti-discord-manual-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, get-update-version, set-public-provider, sync-temp-to-latest] + 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 @@ -130,21 +188,28 @@ jobs: new_version: ${{ needs.get-update-version.outputs.new_version }} - comment-pr-build-url: - needs: [build-macos, build-windows-x64, build-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/ + # 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" + # - 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 index 7d69a5c12..a223027f3 100644 --- a/.github/workflows/jan-electron-build.yml +++ b/.github/workflows/jan-electron-build.yml @@ -33,8 +33,8 @@ jobs: draft: true prerelease: false - build-macos: - uses: ./.github/workflows/template-build-macos.yml + build-electron-macos: + uses: ./.github/workflows/template-electron-build-macos.yml secrets: inherit needs: [get-update-version] with: @@ -44,8 +44,8 @@ jobs: nightly: false new_version: ${{ needs.get-update-version.outputs.new_version }} - build-windows-x64: - uses: ./.github/workflows/template-build-windows-x64.yml + build-electron-windows-x64: + uses: ./.github/workflows/template-electron-build-windows-x64.yml secrets: inherit needs: [get-update-version] with: @@ -55,8 +55,8 @@ jobs: nightly: false new_version: ${{ needs.get-update-version.outputs.new_version }} - build-linux-x64: - uses: ./.github/workflows/template-build-linux-x64.yml + build-electron-linux-x64: + uses: ./.github/workflows/template-electron-build-linux-x64.yml secrets: inherit needs: [get-update-version] with: @@ -65,9 +65,49 @@ jobs: 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-macos, build-windows-x64, build-linux-x64] + 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 diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 8cf5ebab4..6c2842c30 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -34,6 +34,8 @@ on: - 'Makefile' - 'extensions/**' - 'core/**' + - 'src-tauri/**' + - 'web-app/**' - '!README.md' jobs: @@ -53,7 +55,6 @@ jobs: run: | make config-yarn yarn - yarn build:joi yarn build:core - name: Run test coverage @@ -305,52 +306,53 @@ jobs: path: electron/playwright-report/ retention-days: 2 - coverage-check: - runs-on: ubuntu-latest - needs: base_branch_cov - 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' - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - fetch-depth: 0 + # coverage-check: + # runs-on: ubuntu-latest + # needs: base_branch_cov + # continue-on-error: true + # 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' + # 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: Installing node + # uses: actions/setup-node@v3 + # with: + # node-version: 20 - - name: Install yarn - run: npm install -g yarn + # - name: Install yarn + # run: npm install -g yarn - - name: 'Cleanup cache' - continue-on-error: true - run: | - rm -rf ~/jan - make clean + # - name: 'Cleanup cache' + # continue-on-error: true + # run: | + # rm -rf ~/jan + # make clean - - name: Download code coverage report from base branch - uses: actions/download-artifact@v4 - with: - name: ref-lcov.info + # - name: Download code coverage report from base branch + # uses: actions/download-artifact@v4 + # with: + # name: ref-lcov.info - - name: Linter and test coverage - run: | - export DISPLAY=$(w -h | awk 'NR==1 {print $2}') - echo -e "Display ID: $DISPLAY" - make lint - yarn build:test - yarn test:coverage + # - name: Linter and test coverage + # run: | + # export DISPLAY=$(w -h | awk 'NR==1 {print $2}') + # echo -e "Display ID: $DISPLAY" + # make lint + # yarn build:test + # yarn test:coverage - - name: Generate Code Coverage report - id: code-coverage - uses: barecheck/code-coverage-action@v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - lcov-file: './coverage/lcov.info' - base-lcov-file: './lcov.info' - send-summary-comment: true - show-annotations: 'warning' + # - name: Generate Code Coverage report + # id: code-coverage + # uses: barecheck/code-coverage-action@v1 + # with: + # github-token: ${{ secrets.GITHUB_TOKEN }} + # lcov-file: './coverage/lcov.info' + # base-lcov-file: './lcov.info' + # send-summary-comment: true + # show-annotations: 'warning' test-on-ubuntu-pr-target: runs-on: ubuntu-latest diff --git a/.github/workflows/jan-tauri-build-beta.yml b/.github/workflows/jan-tauri-build-beta.yml new file mode 100644 index 000000000..476293d71 --- /dev/null +++ b/.github/workflows/jan-tauri-build-beta.yml @@ -0,0 +1,156 @@ +name: Tauri Builder - Beta Build + +on: + push: + tags: ["v[0-9]+.[0-9]+.[0-9]+-rc[0-9]+-beta"] + +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 + generate_release_notes: true + + build-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 + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-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 + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + build-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 + new_version: ${{ needs.get-update-version.outputs.new_version }} + channel: beta + cortex_api_port: "39271" + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + + sync-temp-to-latest: + needs: [create-draft-release, get-update-version, build-macos, build-windows-x64, build-linux-x64] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + + - 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-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://delta.jan.ai/beta/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://delta.jan.ai/beta/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" + DARWIN_URL="https://delta.jan.ai/beta/Jan-beta_${{ 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 + run: | + # sync temp-beta to beta by copy files that are different or new + aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/latest.json + aws s3 sync "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-beta/" "s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/beta/" + 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" + + - name: Upload release assert if public provider is github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + asset_path: ./latest.json + asset_name: latest.json + asset_content_type: text/json + + noti-discord-and-update-url-readme: + needs: [build-macos, get-update-version, build-windows-x64, build-linux-x64, sync-temp-to-latest] + runs-on: ubuntu-latest + steps: + - name: Set version to environment variable + run: | + VERSION=${{ needs.get-update-version.outputs.new_version }} + VERSION="${VERSION#v}" + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Notify Discord + uses: Ilshidur/action-discord@master + with: + args: | + Jan-beta App version {{ VERSION }}, has been released, use the following links to download the app with faster speed or visit the Github release page for more information: + - Windows: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_x64-setup.exe + - macOS Universal: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_universal.dmg + - Linux Deb: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64.deb + - Linux AppImage: https://delta.jan.ai/beta/Jan-beta_{{ VERSION }}_amd64.AppImage + env: + DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }} \ No newline at end of file diff --git a/.github/workflows/jan-tauri-build-nightly.yaml b/.github/workflows/jan-tauri-build-nightly.yaml index ebc8b5709..5d89ee06a 100644 --- a/.github/workflows/jan-tauri-build-nightly.yaml +++ b/.github/workflows/jan-tauri-build-nightly.yaml @@ -1,225 +1,225 @@ -name: Tauri 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-macos: - uses: ./.github/workflows/template-tauri-build-macos.yml - needs: [get-update-version, set-public-provider] - secrets: inherit - 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-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-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-windows-x64, - build-linux-x64, - build-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-linux-x64.outputs.APPIMAGE_SIG }}" - LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" - WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" - WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-windows-x64.outputs.FILE_NAME }}" - DARWIN_SIGNATURE="${{ needs.build-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-macos, - build-windows-x64, - build-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-macos, - build-windows-x64, - build-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-macos, - build-windows-x64, - build-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-macos, - build-windows-x64, - build-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" +name: Tauri 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-macos: + uses: ./.github/workflows/template-tauri-build-macos.yml + needs: [get-update-version, set-public-provider] + secrets: inherit + 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-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-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-windows-x64, + build-linux-x64, + build-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-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-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-macos, + build-windows-x64, + build-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-macos, + build-windows-x64, + build-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-macos, + build-windows-x64, + build-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-macos, + build-windows-x64, + build-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" diff --git a/.github/workflows/jan-tauri-build.yaml b/.github/workflows/jan-tauri-build.yaml new file mode 100644 index 000000000..1dc22f2e4 --- /dev/null +++ b/.github/workflows/jan-tauri-build.yaml @@ -0,0 +1,145 @@ +name: Tauri 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-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-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-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 }} + + sync-temp-to-latest: + needs: [create-draft-release, get-update-version, build-macos, build-windows-x64, build-linux-x64] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + + - 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-linux-x64.outputs.APPIMAGE_SIG }}" + LINUX_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }}" + WINDOWS_SIGNATURE="${{ needs.build-windows-x64.outputs.WIN_SIG }}" + WINDOWS_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-windows-x64.outputs.FILE_NAME }}" + DARWIN_SIGNATURE="${{ needs.build-macos.outputs.MAC_UNIVERSAL_SIG }}" + DARWIN_URL="https://github.com/menloresearch/jan/releases/download/v${{ needs.get-update-version.outputs.new_version }}/${{ needs.build-macos.outputs.TAR_NAME }}" + + 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: Upload release assert if public provider is github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ needs.create-draft-release.outputs.upload_url }} + asset_path: ./latest.json + asset_name: latest.json + asset_content_type: text/json + + update_release_draft: + needs: [build-macos, build-windows-x64, build-linux-x64] + 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/nightly-integrate-cortex-cpp.yml b/.github/workflows/nightly-integrate-cortex-cpp.yml deleted file mode 100644 index 066fbd28e..000000000 --- a/.github/workflows/nightly-integrate-cortex-cpp.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Nightly Update cortex cpp - -on: - schedule: - - cron: '30 19 * * 1-5' # At 01:30 on every day-of-week from Monday through Friday UTC +7 - workflow_dispatch: - -jobs: - update-submodule: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - actions: write - - outputs: - pr_number: ${{ steps.check-update.outputs.pr_number }} - pr_created: ${{ steps.check-update.outputs.pr_created }} - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - ref: dev - fetch-depth: 0 - token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - - - name: Configure Git - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - - name: Update submodule to latest release - id: check-update - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - curl -s https://api.github.com/repos/menloresearch/cortex/releases > /tmp/github_api_releases.json - latest_prerelease_name=$(cat /tmp/github_api_releases.json | jq -r '.[] | select(.prerelease) | .name' | head -n 1) - - get_asset_count() { - local version_name=$1 - cat /tmp/github_api_releases.json | jq -r --arg version_name "$version_name" '.[] | select(.name == $version_name) | .assets | length' - } - - cortex_cpp_version_file_path="extensions/inference-nitro-extension/bin/version.txt" - current_version_name=$(cat "$cortex_cpp_version_file_path" | head -n 1) - - current_version_asset_count=$(get_asset_count "$current_version_name") - latest_prerelease_asset_count=$(get_asset_count "$latest_prerelease_name") - - if [ "$current_version_name" = "$latest_prerelease_name" ]; then - echo "cortex cpp remote repo doesn't have update today, skip update cortex.cpp for today nightly build" - echo "::set-output name=pr_created::false" - exit 0 - fi - - if [ "$current_version_asset_count" != "$latest_prerelease_asset_count" ]; then - echo "Latest prerelease version has different number of assets, somethink went wrong, skip update cortex.cpp for today nightly build" - echo "::set-output name=pr_created::false" - exit 1 - fi - - echo $latest_prerelease_name > $cortex_cpp_version_file_path - echo "Updated version from $current_version_name to $latest_prerelease_name." - echo "::set-output name=pr_created::true" - - git add -f $cortex_cpp_version_file_path - git commit -m "Update cortex cpp nightly to version $latest_prerelease_name" - branch_name="update-nightly-$(date +'%Y-%m-%d-%H-%M')" - git checkout -b $branch_name - git push origin $branch_name - - pr_title="Update cortex cpp nightly to version $latest_prerelease_name" - pr_body="This PR updates the Update cortex cpp nightly to version $latest_prerelease_name" - - gh pr create --title "$pr_title" --body "$pr_body" --head $branch_name --base dev --reviewer Van-QA - - pr_number=$(gh pr list --head $branch_name --json number --jq '.[0].number') - echo "::set-output name=pr_number::$pr_number" - - check-and-merge-pr: - needs: update-submodule - if: needs.update-submodule.outputs.pr_created == 'true' - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - with: - submodules: recursive - fetch-depth: 0 - token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - - - name: Wait for CI to pass - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - pr_number=${{ needs.update-submodule.outputs.pr_number }} - while true; do - ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt') - if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then - echo "CI is still running, waiting..." - sleep 60 - else - echo "CI has completed, checking states..." - ci_states=$(gh pr checks $pr_number --json state --jq '.[].state') - if echo "$ci_states" | grep -vqE "SUCCESS|SKIPPED"; then - echo "CI failed, exiting..." - exit 1 - else - echo "CI passed, merging PR..." - break - fi - fi - done - - - name: Merge the PR - env: - GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }} - run: | - pr_number=${{ needs.update-submodule.outputs.pr_number }} - gh pr merge $pr_number --merge --admin diff --git a/.github/workflows/publish-npm-core.yml b/.github/workflows/publish-npm-core.yml index 462dbdc8e..403deb100 100644 --- a/.github/workflows/publish-npm-core.yml +++ b/.github/workflows/publish-npm-core.yml @@ -1,10 +1,9 @@ name: Publish core Package to npmjs on: push: - tags: ["v[0-9]+.[0-9]+.[0-9]+-core"] - paths: ["core/**", ".github/workflows/publish-npm-core.yml"] - pull_request: - paths: ["core/**", ".github/workflows/publish-npm-core.yml"] + tags: ['v[0-9]+.[0-9]+.[0-9]+-core'] + paths: ['core/**', '.github/workflows/publish-npm-core.yml'] + workflow_dispatch: jobs: build-and-publish-plugins: environment: production @@ -12,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: "0" + fetch-depth: '0' token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - name: Install jq @@ -24,7 +23,7 @@ jobs: env: GITHUB_REF: ${{ github.ref }} - - name: "Get Semantic Version from tag" + - name: 'Get Semantic Version from tag' if: github.event_name == 'push' run: | # Get the tag from the event @@ -42,8 +41,8 @@ jobs: # Setup .npmrc file to publish to npm - uses: actions/setup-node@v3 with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" + node-version: '20.x' + registry-url: 'https://registry.npmjs.org' - run: cd core && corepack enable && corepack prepare yarn@4.5.3 --activate && yarn --version && yarn config set -H enableImmutableInstalls false && yarn install && yarn build diff --git a/.github/workflows/publish-npm-joi.yml b/.github/workflows/publish-npm-joi.yml deleted file mode 100644 index 867ad80fe..000000000 --- a/.github/workflows/publish-npm-joi.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Publish joi Package to npmjs -on: - push: - tags: ["v[0-9]+.[0-9]+.[0-9]+-joi"] - paths: ["joi/**", ".github/workflows/publish-npm-joi.yml"] - pull_request: - paths: ["joi/**", ".github/workflows/publish-npm-joi.yml"] -jobs: - build-and-publish-plugins: - environment: production - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: "0" - token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - - - name: Install jq - uses: dcarbone/install-jq-action@v2.0.1 - - - 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: "Get Semantic Version from tag" - if: github.event_name == 'push' - run: | - # Get the tag from the event - tag=${GITHUB_REF#refs/tags/v} - # remove the -joi suffix - new_version=$(echo $tag | sed -n 's/-joi//p') - echo $new_version - # Replace the old version with the new version in package.json - jq --arg version "$new_version" '.version = $version' joi/package.json > /tmp/package.json && mv /tmp/package.json joi/package.json - - # Print the new version - echo "Updated package.json version to: $new_version" - cat joi/package.json - - # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v3 - with: - node-version: "20.x" - registry-url: "https://registry.npmjs.org" - - - run: cd joi && corepack enable && corepack prepare yarn@4.5.3 --activate && yarn --version && yarn config set -H enableImmutableInstalls false && yarn install && yarn build - - - run: cd joi && yarn publish --access public - if: github.event_name == 'push' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/template-build-linux-x64.yml b/.github/workflows/template-electron-build-linux-x64.yml similarity index 96% rename from .github/workflows/template-build-linux-x64.yml rename to .github/workflows/template-electron-build-linux-x64.yml index 58b566931..4dfab5c09 100644 --- a/.github/workflows/template-build-linux-x64.yml +++ b/.github/workflows/template-electron-build-linux-x64.yml @@ -41,6 +41,7 @@ on: jobs: build-linux-x64: + if: inputs.public_provider == 'github' || inputs.public_provider == 'none' runs-on: ubuntu-latest environment: production permissions: @@ -130,7 +131,7 @@ jobs: env: VERSION_TAG: ${{ inputs.new_version }} - - name: Build and publish app to aws s3 r2 or github artifactory + - 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 @@ -176,12 +177,12 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-linux-amd64-${{ inputs.new_version }}-deb + 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-linux-amd64-${{ inputs.new_version }}-AppImage + 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-build-macos.yml b/.github/workflows/template-electron-build-macos.yml similarity index 98% rename from .github/workflows/template-build-macos.yml rename to .github/workflows/template-electron-build-macos.yml index a5e5cc724..ab9f002cb 100644 --- a/.github/workflows/template-build-macos.yml +++ b/.github/workflows/template-electron-build-macos.yml @@ -51,6 +51,7 @@ on: jobs: build-macos: + if: inputs.public_provider == 'github' || inputs.public_provider == 'none' runs-on: macos-latest environment: production permissions: @@ -160,7 +161,7 @@ jobs: 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 + - 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 @@ -229,5 +230,5 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-mac-universal-${{ inputs.new_version }} + name: jan-electron-mac-universal-${{ inputs.new_version }} path: ./electron/dist/*.dmg \ No newline at end of file diff --git a/.github/workflows/template-build-windows-x64.yml b/.github/workflows/template-electron-build-windows-x64.yml similarity index 98% rename from .github/workflows/template-build-windows-x64.yml rename to .github/workflows/template-electron-build-windows-x64.yml index 9be028e15..9f71dadb0 100644 --- a/.github/workflows/template-build-windows-x64.yml +++ b/.github/workflows/template-electron-build-windows-x64.yml @@ -51,6 +51,7 @@ on: jobs: build-windows-x64: + if: inputs.public_provider == 'github' || inputs.public_provider == 'none' runs-on: windows-latest permissions: contents: write @@ -225,5 +226,5 @@ jobs: if: inputs.public_provider != 'github' uses: actions/upload-artifact@v4 with: - name: jan-win-x64-${{ inputs.new_version }} + 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 97340be81..70f5eace9 100644 --- a/.github/workflows/template-get-update-version.yml +++ b/.github/workflows/template-get-update-version.yml @@ -44,9 +44,12 @@ jobs: exit 1 } - if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then + if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then echo "Tag detected, set output follow tag" - echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}" + sanitized_tag="${{ steps.tag.outputs.tag }}" + # Remove the 'v' prefix if it exists + sanitized_tag="${sanitized_tag#v}" + echo "::set-output name=new_version::$sanitized_tag" else # Get the latest release tag from GitHub API LATEST_TAG=$(get_latest_tag) 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 282e0aa76..eaaee7e50 100644 --- a/.github/workflows/template-noti-discord-and-update-url-readme.yml +++ b/.github/workflows/template-noti-discord-and-update-url-readme.yml @@ -47,10 +47,10 @@ jobs: with: args: | Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}: - - Windows: https://delta.jan.ai/nightly/jan-nightly-win-x64-{{ VERSION }}.exe - - macOS Universal: https://delta.jan.ai/nightly/jan-nightly-mac-universal-{{ VERSION }}.dmg - - Linux Deb: https://delta.jan.ai/nightly/jan-nightly-linux-amd64-{{ VERSION }}.deb - - Linux AppImage: https://delta.jan.ai/nightly/jan-nightly-linux-x86_64-{{ VERSION }}.AppImage + - Windows: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_x64-setup.exe + - macOS Universal: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_universal.dmg + - Linux Deb: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.deb + - Linux AppImage: https://delta.jan.ai/nightly/Jan-nightly_{{ VERSION }}_amd64.AppImage - Github action run: https://github.com/menloresearch/jan/actions/runs/{{ GITHUB_RUN_ID }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml index 86ad11a46..6c47c79f2 100644 --- a/.github/workflows/template-tauri-build-linux-x64.yml +++ b/.github/workflows/template-tauri-build-linux-x64.yml @@ -1,318 +1,326 @@ -name: tauri-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: '' - cortex_api_port: - required: false - type: string - default: '' - upload_url: - required: false - type: string - default: '' - channel: - required: true - type: string - default: 'nightly' - description: 'The channel to use for this job' - secrets: - DELTA_AWS_S3_BUCKET_NAME: - required: false - DELTA_AWS_ACCESS_KEY_ID: - required: false - DELTA_AWS_SECRET_ACCESS_KEY: - required: false - TAURI_SIGNING_PRIVATE_KEY: - required: false - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: - required: false - TAURI_SIGNING_PUBLIC_KEY: - required: false - outputs: - DEB_SIG: - value: ${{ jobs.build-linux-x64.outputs.DEB_SIG }} - APPIMAGE_SIG: - value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_SIG }} - APPIMAGE_FILE_NAME: - value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }} -jobs: - build-linux-x64: - runs-on: ubuntu-22.04 - outputs: - 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: - - name: Getting the repo - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Free Disk Space Before Build - run: | - echo "Disk space before cleanup:" - df -h - sudo rm -rf /usr/local/.ghcup - sudo rm -rf /opt/hostedtoolcache/CodeQL - sudo rm -rf /usr/local/lib/android/sdk/ndk - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/share/boost - sudo apt-get clean - echo "Disk space after cleanup:" - df -h - - - name: Replace Icons for Beta Build - if: inputs.channel != 'stable' - shell: bash - run: | - cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml - run: | - cargo install ctoml - - - name: Install Tauri dependecies - 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 - - - name: Update app version base public_provider - run: | - echo "Version: ${{ inputs.new_version }}" - # Update tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.resources = ["resources/themes/**/*", "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 - if [ "${{ inputs.channel }}" != "stable" ]; then - jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", - "usr/lib/Jan-${{ inputs.channel }}/binaries": "binaries/deps", - "usr/lib/Jan-${{ inputs.channel }}/binaries/engines": "binaries/engines", - "usr/lib/Jan-${{ inputs.channel }}/binaries/libvulkan.so": "binaries/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - else - jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", - "usr/lib/Jan/binaries": "binaries/deps", - "usr/lib/Jan/binaries/engines": "binaries/engines", - "usr/lib/Jan/binaries/libvulkan.so": "binaries/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - fi - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - - ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" - cat ./src-tauri/Cargo.toml - - # Change app name for beta and nightly builds - if [ "${{ inputs.channel }}" != "stable" ]; then - jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - - chmod +x .github/scripts/rename-tauri-app.sh - .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} - - cat ./src-tauri/tauri.conf.json - - # Update Cargo.toml - ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" - ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" - echo "------------------" - cat ./src-tauri/Cargo.toml - - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} - cat ./package.json - fi - - name: Build app - run: | - make build-tauri - # Copy engines and bun to appimage - wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool - chmod +x ./appimagetool - if [ "${{ inputs.channel }}" != "stable" ]; then - ls ./src-tauri/target/release/bundle/appimage/ - cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/bin/bun - mkdir -p ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/engines - cp -f ./src-tauri/binaries/deps/*.so* ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/ - cp -f ./src-tauri/binaries/*.so* ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/ - cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/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 - ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir $APP_IMAGE - else - 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 - ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE - fi - - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} - - # Publish app - - ## Artifacts, for dev and test - - name: Upload Artifact - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-linux-amd64-${{ inputs.new_version }}-deb - path: ./src-tauri/target/release/bundle/deb/*.deb - - - name: Upload Artifact - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-linux-amd64-${{ inputs.new_version }}-AppImage - path: ./src-tauri/target/release/bundle/appimage/*.AppImage - - ## create zip file and latest-linux.yml for linux electron auto updater - - name: Create zip file and latest-linux.yml for linux electron auto updater - id: packageinfo - run: | - cd ./src-tauri/target/release/bundle - - if [ "${{ inputs.channel }}" != "stable" ]; then - DEB_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb - APPIMAGE_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage - DEB_SIG=$(cat deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig) - APPIMAGE_SIG=$(cat appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig) - else - DEB_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.deb - APPIMAGE_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.AppImage - DEB_SIG=$(cat deb/Jan_${{ inputs.new_version }}_amd64.deb.sig) - APPIMAGE_SIG=$(cat appimage/Jan_${{ inputs.new_version }}_amd64.AppImage.sig) - fi - - DEB_FILE_SIZE=$(stat -c%s deb/$DEB_FILE_NAME) - APPIMAGE_FILE_SIZE=$(stat -c%s appimage/$APPIMAGE_FILE_NAME) - echo "deb file size: $DEB_FILE_SIZE" - echo "appimage file size: $APPIMAGE_FILE_SIZE" - - DEB_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py deb/$DEB_FILE_NAME) - APPIMAGE_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py appimage/$APPIMAGE_FILE_NAME) - echo "deb sh512 checksum: $DEB_SH512_CHECKSUM" - echo "appimage sh512 checksum: $APPIMAGE_SH512_CHECKSUM" - - CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") - echo "releaseDate: $CURRENT_TIME" - - # Create latest-linux.yml file - echo "version: ${{ inputs.new_version }}" > latest-linux.yml - echo "files:" >> latest-linux.yml - echo " - url: $DEB_FILE_NAME" >> latest-linux.yml - echo " sha512: $DEB_SH512_CHECKSUM" >> latest-linux.yml - echo " size: $DEB_FILE_SIZE" >> latest-linux.yml - echo " - url: $APPIMAGE_FILE_NAME" >> latest-linux.yml - echo " sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml - echo " size: $APPIMAGE_FILE_SIZE" >> latest-linux.yml - echo "path: $APPIMAGE_FILE_NAME" >> latest-linux.yml - echo "sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml - echo "releaseDate: $CURRENT_TIME" >> latest-linux.yml - - cat latest-linux.yml - cp latest-linux.yml beta-linux.yml - - echo "DEB_SIG=$DEB_SIG" >> $GITHUB_OUTPUT - echo "APPIMAGE_SIG=$APPIMAGE_SIG" >> $GITHUB_OUTPUT - echo "DEB_FILE_NAME=$DEB_FILE_NAME" >> $GITHUB_OUTPUT - echo "APPIMAGE_FILE_NAME=$APPIMAGE_FILE_NAME" >> $GITHUB_OUTPUT - - ## Upload to s3 for nightly and beta - - name: upload to aws s3 if public provider is aws - if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' - run: | - cd ./src-tauri/target/release/bundle - - # Upload for electron updater for nightly - aws s3 cp ./latest-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-linux.yml - aws s3 cp ./beta-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-linux.yml - - # Upload for tauri updater - aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage - aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb - aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig - aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig - 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' - - ## Upload to github release for stable release - - name: Upload release assert if public provider is github - if: inputs.channel == 'stable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/latest-linux.yml - asset_name: latest-linux.yml - asset_content_type: text/yaml - - - name: Upload release assert if public provider is github - if: inputs.channel == 'beta' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/beta-linux.yml - asset_name: beta-linux.yml - asset_content_type: text/yaml - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/appimage/${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} - asset_name: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} - asset_content_type: application/octet-stream - - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/deb/${{ steps.packageinfo.outputs.DEB_FILE_NAME }} - asset_name: ${{ steps.packageinfo.outputs.DEB_FILE_NAME }} - asset_content_type: application/octet-stream +name: tauri-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: '' + cortex_api_port: + required: false + type: string + default: '' + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + secrets: + DELTA_AWS_S3_BUCKET_NAME: + required: false + DELTA_AWS_ACCESS_KEY_ID: + required: false + DELTA_AWS_SECRET_ACCESS_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + DEB_SIG: + value: ${{ jobs.build-linux-x64.outputs.DEB_SIG }} + APPIMAGE_SIG: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_SIG }} + APPIMAGE_FILE_NAME: + value: ${{ jobs.build-linux-x64.outputs.APPIMAGE_FILE_NAME }} +jobs: + build-linux-x64: + runs-on: ubuntu-22.04 + outputs: + 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: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Free Disk Space Before Build + run: | + echo "Disk space before cleanup:" + df -h + sudo rm -rf /usr/local/.ghcup + sudo rm -rf /opt/hostedtoolcache/CodeQL + sudo rm -rf /usr/local/lib/android/sdk/ndk + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo apt-get clean + echo "Disk space after cleanup:" + df -h + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml + run: | + cargo install ctoml + + - name: Install Tauri dependecies + 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 + + - name: Update app version base public_provider + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .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 + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", + "usr/lib/Jan-${{ inputs.channel }}/binaries": "binaries/deps", + "usr/lib/Jan-${{ inputs.channel }}/binaries/engines": "binaries/engines", + "usr/lib/Jan-${{ inputs.channel }}/binaries/libvulkan.so": "binaries/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + else + jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun", + "usr/lib/Jan/binaries": "binaries/deps", + "usr/lib/Jan/binaries/engines": "binaries/engines", + "usr/lib/Jan/binaries/libvulkan.so": "binaries/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + fi + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json + mv /tmp/package.json web-app/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Build app + run: | + make build-tauri + # Copy engines and bun to appimage + wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool + chmod +x ./appimagetool + if [ "${{ inputs.channel }}" != "stable" ]; then + ls ./src-tauri/target/release/bundle/appimage/ + cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/bin/bun + mkdir -p ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/engines + cp -f ./src-tauri/binaries/deps/*.so* ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/ + cp -f ./src-tauri/binaries/*.so* ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/binaries/ + cp -rf ./src-tauri/binaries/engines ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir/usr/lib/Jan-${{ inputs.channel }}/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 + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan-${{ inputs.channel }}.AppDir $APP_IMAGE + yarn tauri signer sign \ + --private-key "$TAURI_SIGNING_PRIVATE_KEY" \ + --password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \ + "$APP_IMAGE" + else + 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 + ./appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE + yarn tauri signer sign \ + --private-key "$TAURI_SIGNING_PRIVATE_KEY" \ + --password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \ + "$APP_IMAGE" + fi + + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-linux-amd64-${{ inputs.new_version }}-deb + path: ./src-tauri/target/release/bundle/deb/*.deb + + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-linux-amd64-${{ inputs.new_version }}-AppImage + path: ./src-tauri/target/release/bundle/appimage/*.AppImage + + ## create zip file and latest-linux.yml for linux electron auto updater + - name: Create zip file and latest-linux.yml for linux electron auto updater + id: packageinfo + run: | + cd ./src-tauri/target/release/bundle + + if [ "${{ inputs.channel }}" != "stable" ]; then + DEB_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig) + else + DEB_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.deb + APPIMAGE_FILE_NAME=Jan_${{ inputs.new_version }}_amd64.AppImage + DEB_SIG=$(cat deb/Jan_${{ inputs.new_version }}_amd64.deb.sig) + APPIMAGE_SIG=$(cat appimage/Jan_${{ inputs.new_version }}_amd64.AppImage.sig) + fi + + DEB_FILE_SIZE=$(stat -c%s deb/$DEB_FILE_NAME) + APPIMAGE_FILE_SIZE=$(stat -c%s appimage/$APPIMAGE_FILE_NAME) + echo "deb file size: $DEB_FILE_SIZE" + echo "appimage file size: $APPIMAGE_FILE_SIZE" + + DEB_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py deb/$DEB_FILE_NAME) + APPIMAGE_SH512_CHECKSUM=$(python3 ../../../../.github/scripts/electron-checksum.py appimage/$APPIMAGE_FILE_NAME) + echo "deb sh512 checksum: $DEB_SH512_CHECKSUM" + echo "appimage sh512 checksum: $APPIMAGE_SH512_CHECKSUM" + + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest-linux.yml file + echo "version: ${{ inputs.new_version }}" > latest-linux.yml + echo "files:" >> latest-linux.yml + echo " - url: $DEB_FILE_NAME" >> latest-linux.yml + echo " sha512: $DEB_SH512_CHECKSUM" >> latest-linux.yml + echo " size: $DEB_FILE_SIZE" >> latest-linux.yml + echo " - url: $APPIMAGE_FILE_NAME" >> latest-linux.yml + echo " sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml + echo " size: $APPIMAGE_FILE_SIZE" >> latest-linux.yml + echo "path: $APPIMAGE_FILE_NAME" >> latest-linux.yml + echo "sha512: $APPIMAGE_SH512_CHECKSUM" >> latest-linux.yml + echo "releaseDate: $CURRENT_TIME" >> latest-linux.yml + + cat latest-linux.yml + cp latest-linux.yml beta-linux.yml + + echo "DEB_SIG=$DEB_SIG" >> $GITHUB_OUTPUT + echo "APPIMAGE_SIG=$APPIMAGE_SIG" >> $GITHUB_OUTPUT + echo "DEB_FILE_NAME=$DEB_FILE_NAME" >> $GITHUB_OUTPUT + echo "APPIMAGE_FILE_NAME=$APPIMAGE_FILE_NAME" >> $GITHUB_OUTPUT + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/release/bundle + + # Upload for electron updater for nightly + aws s3 cp ./latest-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-linux.yml + aws s3 cp ./beta-linux.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-linux.yml + + # Upload for tauri updater + aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage + aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb + aws s3 cp ./appimage/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.AppImage.sig + aws s3 cp ./deb/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_amd64.deb.sig + 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' + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/latest-linux.yml + asset_name: latest-linux.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/beta-linux.yml + asset_name: beta-linux.yml + asset_content_type: text/yaml + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/appimage/${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.APPIMAGE_FILE_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/deb/${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_name: ${{ steps.packageinfo.outputs.DEB_FILE_NAME }} + asset_content_type: application/octet-stream diff --git a/.github/workflows/template-tauri-build-macos.yml b/.github/workflows/template-tauri-build-macos.yml index 7c5bda4de..6999ff77e 100644 --- a/.github/workflows/template-tauri-build-macos.yml +++ b/.github/workflows/template-tauri-build-macos.yml @@ -1,312 +1,312 @@ -name: tauri-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: '' - cortex_api_port: - required: false - type: string - default: '' - upload_url: - required: false - type: string - default: '' - channel: - required: true - type: string - default: 'nightly' - description: 'The channel to use for this job' - 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 - TAURI_SIGNING_PRIVATE_KEY: - required: false - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: - required: false - TAURI_SIGNING_PUBLIC_KEY: - required: false - outputs: - MAC_UNIVERSAL_SIG: - value: ${{ jobs.build-macos.outputs.MAC_UNIVERSAL_SIG }} - TAR_NAME: - value: ${{ jobs.build-macos.outputs.TAR_NAME }} - -jobs: - build-macos: - runs-on: macos-latest - outputs: - MAC_UNIVERSAL_SIG: ${{ steps.metadata.outputs.MAC_UNIVERSAL_SIG }} - TAR_NAME: ${{ steps.metadata.outputs.TAR_NAME }} - 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.channel != 'stable' - shell: bash - run: | - cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml - run: | - cargo install ctoml - - - name: Create bun and uv universal - run: | - mkdir -p ./src-tauri/resources/bin/ - cd ./src-tauri/resources/bin/ - curl -L -o bun-darwin-x64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-x64.zip - curl -L -o bun-darwin-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-aarch64.zip - unzip bun-darwin-x64.zip - unzip bun-darwin-aarch64.zip - lipo -create -output bun-universal-apple-darwin bun-darwin-x64/bun bun-darwin-aarch64/bun - cp -f bun-darwin-aarch64/bun bun-aarch64-apple-darwin - cp -f bun-darwin-x64/bun bun-x86_64-apple-darwin - cp -f bun-universal-apple-darwin bun - - curl -L -o uv-x86_64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86_64-apple-darwin.tar.gz - curl -L -o uv-arm64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz - tar -xzf uv-x86_64.tar.gz - tar -xzf uv-arm64.tar.gz - mv uv-x86_64-apple-darwin uv-x86_64 - mv uv-aarch64-apple-darwin uv-aarch64 - lipo -create -output uv-universal-apple-darwin uv-x86_64/uv uv-aarch64/uv - cp -f uv-x86_64/uv uv-x86_64-apple-darwin - cp -f uv-aarch64/uv uv-aarch64-apple-darwin - cp -f uv-universal-apple-darwin uv - ls -la - - - name: Update app version based on latest release tag with build number - run: | - echo "Version: ${{ inputs.new_version }}" - # Update tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - - ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" - cat ./src-tauri/Cargo.toml - - # Change app name for beta and nightly builds - if [ "${{ inputs.channel }}" != "stable" ]; then - jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - - chmod +x .github/scripts/rename-tauri-app.sh - .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} - - cat ./src-tauri/tauri.conf.json - - # Update Cargo.toml - ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" - ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" - echo "------------------" - cat ./src-tauri/Cargo.toml - - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} - cat ./package.json - fi - - name: Get key for notarize - run: base64 -d <<< "$NOTARIZE_P8_BASE64" > /tmp/notary-key.p8 - shell: bash - env: - NOTARIZE_P8_BASE64: ${{ secrets.NOTARIZE_P8_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 app - run: | - rustup target add x86_64-apple-darwin - make build-tauri - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - APP_PATH: '.' - POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} - POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} - # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} - APPLE_CERTIFICATE: ${{ secrets.CODE_SIGN_P12_BASE64 }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} - APPLE_API_ISSUER: ${{ secrets.NOTARY_ISSUER }} - APPLE_API_KEY: ${{ secrets.NOTARY_KEY_ID }} - APPLE_API_KEY_PATH: /tmp/notary-key.p8 - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} - - # Publish app - - ## Artifacts, for dev and test - - name: Upload Artifact - if: inputs.public_provider != 'github' - uses: actions/upload-artifact@v4 - with: - name: jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.dmg - path: | - ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg - - ## create zip file and latest-mac.yml for mac electron auto updater - - name: create zip file and latest-mac.yml for mac electron auto updater - run: | - cd ./src-tauri/target/universal-apple-darwin/release/bundle/macos - if [ "${{ inputs.channel }}" != "stable" ]; then - zip -r jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip Jan-${{ inputs.channel }}.app - FILE_NAME=jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip - DMG_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg - MAC_UNIVERSAL_SIG=$(cat Jan-${{ inputs.channel }}.app.tar.gz.sig) - TAR_NAME=Jan-${{ inputs.channel }}.app.tar.gz - else - zip -r jan-mac-universal-${{ inputs.new_version }}.zip Jan.app - FILE_NAME=jan-mac-universal-${{ inputs.new_version }}.zip - MAC_UNIVERSAL_SIG=$(cat Jan.app.tar.gz.sig) - DMG_NAME=Jan_${{ inputs.new_version }}_universal.dmg - TAR_NAME=Jan.app.tar.gz - fi - - FILE_SIZE=$(stat -f%z $FILE_NAME) - echo "size: $FILE_SIZE" - - SH512_CHECKSUM=$(python3 ../../../../../../.github/scripts/electron-checksum.py $FILE_NAME) - echo "sha512: $SH512_CHECKSUM" - CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") - echo "releaseDate: $CURRENT_TIME" - - # Create latest-mac.yml file - echo "version: ${{ inputs.new_version }}" > latest-mac.yml - echo "files:" >> latest-mac.yml - echo " - url: $FILE_NAME" >> latest-mac.yml - echo " sha512: $SH512_CHECKSUM" >> latest-mac.yml - echo " size: $FILE_NAME" >> latest-mac.yml - echo "path: $FILE_NAME" >> latest-mac.yml - echo "sha512: $SH512_CHECKSUM" >> latest-mac.yml - echo "releaseDate: $CURRENT_TIME" >> latest-mac.yml - - cat latest-mac.yml - cp latest-mac.yml beta-mac.yml - - echo "::set-output name=MAC_UNIVERSAL_SIG::$MAC_UNIVERSAL_SIG" - echo "::set-output name=FILE_NAME::$FILE_NAME" - echo "::set-output name=DMG_NAME::$DMG_NAME" - echo "::set-output name=TAR_NAME::$TAR_NAME" - id: metadata - - ## Upload to s3 for nightly and beta - - name: upload to aws s3 if public provider is aws - if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' - run: | - cd ./src-tauri/target/universal-apple-darwin/release/bundle - - # Upload for electron updater for nightly - aws s3 cp ./macos/latest-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-mac.yml - aws s3 cp ./macos/beta-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-mac.yml - aws s3 cp ./macos/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip - aws s3 cp ./macos/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip.sig - - # Upload for tauri updater - aws s3 cp ./dmg/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg - aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}.app.tar.gz - aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}.app.tar.gz.sig - 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' - - ## Upload to github release for stable release - - name: Upload release assert if public provider is github - if: inputs.channel == 'stable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/latest-mac.yml - asset_name: latest-mac.yml - asset_content_type: text/yaml - - - name: Upload release assert if public provider is github - if: inputs.channel == 'beta' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/beta-mac.yml - asset_name: beta-mac.yml - asset_content_type: text/yaml - - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.FILE_NAME }} - asset_name: ${{ steps.metadata.outputs.FILE_NAME }} - asset_content_type: application/gzip - - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/${{ steps.metadata.outputs.DMG_NAME }} - asset_name: ${{ steps.metadata.outputs.DMG_NAME }} - asset_content_type: application/octet-stream - - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.TAR_NAME }} - asset_name: ${{ steps.metadata.outputs.TAR_NAME }} - asset_content_type: application/gzip +name: tauri-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: '' + cortex_api_port: + required: false + type: string + default: '' + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + 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 + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + MAC_UNIVERSAL_SIG: + value: ${{ jobs.build-macos.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: + value: ${{ jobs.build-macos.outputs.TAR_NAME }} + +jobs: + build-macos: + runs-on: macos-latest + outputs: + MAC_UNIVERSAL_SIG: ${{ steps.metadata.outputs.MAC_UNIVERSAL_SIG }} + TAR_NAME: ${{ steps.metadata.outputs.TAR_NAME }} + 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.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml + run: | + cargo install ctoml + + - name: Create bun and uv universal + run: | + mkdir -p ./src-tauri/resources/bin/ + cd ./src-tauri/resources/bin/ + curl -L -o bun-darwin-x64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-x64.zip + curl -L -o bun-darwin-aarch64.zip https://github.com/oven-sh/bun/releases/download/bun-v1.2.10/bun-darwin-aarch64.zip + unzip bun-darwin-x64.zip + unzip bun-darwin-aarch64.zip + lipo -create -output bun-universal-apple-darwin bun-darwin-x64/bun bun-darwin-aarch64/bun + cp -f bun-darwin-aarch64/bun bun-aarch64-apple-darwin + cp -f bun-darwin-x64/bun bun-x86_64-apple-darwin + cp -f bun-universal-apple-darwin bun + + curl -L -o uv-x86_64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-x86_64-apple-darwin.tar.gz + curl -L -o uv-arm64.tar.gz https://github.com/astral-sh/uv/releases/download/0.6.17/uv-aarch64-apple-darwin.tar.gz + tar -xzf uv-x86_64.tar.gz + tar -xzf uv-arm64.tar.gz + mv uv-x86_64-apple-darwin uv-x86_64 + mv uv-aarch64-apple-darwin uv-aarch64 + lipo -create -output uv-universal-apple-darwin uv-x86_64/uv uv-aarch64/uv + cp -f uv-x86_64/uv uv-x86_64-apple-darwin + cp -f uv-aarch64/uv uv-aarch64-apple-darwin + cp -f uv-universal-apple-darwin uv + ls -la + + - name: Update app version based on latest release tag with build number + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json + mv /tmp/package.json web-app/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + cat ./src-tauri/Cargo.toml + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + fi + - name: Get key for notarize + run: base64 -d <<< "$NOTARIZE_P8_BASE64" > /tmp/notary-key.p8 + shell: bash + env: + NOTARIZE_P8_BASE64: ${{ secrets.NOTARIZE_P8_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 app + run: | + rustup target add x86_64-apple-darwin + make build-tauri + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APP_PATH: '.' + POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }} + POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }} + # CORTEX_API_PORT: ${{ inputs.cortex_api_port }} + APPLE_CERTIFICATE: ${{ secrets.CODE_SIGN_P12_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }} + APPLE_API_ISSUER: ${{ secrets.NOTARY_ISSUER }} + APPLE_API_KEY: ${{ secrets.NOTARY_KEY_ID }} + APPLE_API_KEY_PATH: /tmp/notary-key.p8 + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + # Publish app + + ## Artifacts, for dev and test + - name: Upload Artifact + if: inputs.public_provider != 'github' + uses: actions/upload-artifact@v4 + with: + name: jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.dmg + path: | + ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg + + ## create zip file and latest-mac.yml for mac electron auto updater + - name: create zip file and latest-mac.yml for mac electron auto updater + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle/macos + if [ "${{ inputs.channel }}" != "stable" ]; then + zip -r jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip Jan-${{ inputs.channel }}.app + FILE_NAME=jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip + DMG_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg + MAC_UNIVERSAL_SIG=$(cat Jan-${{ inputs.channel }}.app.tar.gz.sig) + TAR_NAME=Jan-${{ inputs.channel }}.app.tar.gz + else + zip -r jan-mac-universal-${{ inputs.new_version }}.zip Jan.app + FILE_NAME=jan-mac-universal-${{ inputs.new_version }}.zip + MAC_UNIVERSAL_SIG=$(cat Jan.app.tar.gz.sig) + DMG_NAME=Jan_${{ inputs.new_version }}_universal.dmg + TAR_NAME=Jan.app.tar.gz + fi + + FILE_SIZE=$(stat -f%z $FILE_NAME) + echo "size: $FILE_SIZE" + + SH512_CHECKSUM=$(python3 ../../../../../../.github/scripts/electron-checksum.py $FILE_NAME) + echo "sha512: $SH512_CHECKSUM" + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest-mac.yml file + echo "version: ${{ inputs.new_version }}" > latest-mac.yml + echo "files:" >> latest-mac.yml + echo " - url: $FILE_NAME" >> latest-mac.yml + echo " sha512: $SH512_CHECKSUM" >> latest-mac.yml + echo " size: $FILE_SIZE" >> latest-mac.yml + echo "path: $FILE_NAME" >> latest-mac.yml + echo "sha512: $SH512_CHECKSUM" >> latest-mac.yml + echo "releaseDate: $CURRENT_TIME" >> latest-mac.yml + + cat latest-mac.yml + cp latest-mac.yml beta-mac.yml + + echo "::set-output name=MAC_UNIVERSAL_SIG::$MAC_UNIVERSAL_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + echo "::set-output name=DMG_NAME::$DMG_NAME" + echo "::set-output name=TAR_NAME::$TAR_NAME" + id: metadata + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/universal-apple-darwin/release/bundle + + # Upload for electron updater for nightly + aws s3 cp ./macos/latest-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest-mac.yml + aws s3 cp ./macos/beta-mac.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta-mac.yml + aws s3 cp ./macos/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip + # aws s3 cp ./macos/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/jan-${{ inputs.channel }}-mac-universal-${{ inputs.new_version }}.zip.sig + + # Upload for tauri updater + aws s3 cp ./dmg/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}_universal.dmg + aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}.app.tar.gz + aws s3 cp ./macos/Jan-${{ inputs.channel }}.app.tar.gz.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/Jan-${{ inputs.channel }}_${{ inputs.new_version }}.app.tar.gz.sig + 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' + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/latest-mac.yml + asset_name: latest-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/beta-mac.yml + asset_name: beta-mac.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/gzip + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/dmg/${{ steps.metadata.outputs.DMG_NAME }} + asset_name: ${{ steps.metadata.outputs.DMG_NAME }} + asset_content_type: application/octet-stream + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/universal-apple-darwin/release/bundle/macos/${{ steps.metadata.outputs.TAR_NAME }} + asset_name: ${{ steps.metadata.outputs.TAR_NAME }} + asset_content_type: application/gzip diff --git a/.github/workflows/template-tauri-build-windows-x64.yml b/.github/workflows/template-tauri-build-windows-x64.yml index 8ae73731b..47b5663cb 100644 --- a/.github/workflows/template-tauri-build-windows-x64.yml +++ b/.github/workflows/template-tauri-build-windows-x64.yml @@ -1,290 +1,293 @@ -name: tauri-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: '' - cortex_api_port: - required: false - type: string - default: '' - upload_url: - required: false - type: string - default: '' - channel: - required: true - type: string - default: 'nightly' - description: 'The channel to use for this job' - 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 - TAURI_SIGNING_PRIVATE_KEY: - required: false - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: - required: false - TAURI_SIGNING_PUBLIC_KEY: - required: false - outputs: - WIN_SIG: - value: ${{ jobs.build-windows-x64.outputs.WIN_SIG }} - FILE_NAME: - value: ${{ jobs.build-windows-x64.outputs.FILE_NAME }} - -jobs: - build-windows-x64: - runs-on: windows-latest - outputs: - WIN_SIG: ${{ steps.metadata.outputs.WIN_SIG }} - FILE_NAME: ${{ steps.metadata.outputs.FILE_NAME }} - permissions: - contents: write - steps: - - name: Getting the repo - uses: actions/checkout@v3 - with: - ref: ${{ inputs.ref }} - - - name: Replace Icons for Beta Build - if: inputs.channel != 'stable' - shell: bash - run: | - cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml - run: | - cargo install ctoml - - - name: Update app version base on tag - id: version_update - shell: bash - run: | - echo "Version: ${{ inputs.new_version }}" - # Update tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.windows.nsis.template = "tauri.bundle.windows.nsis.template"' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json - mv /tmp/package.json web/package.json - - ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" - echo "---------Cargo.toml---------" - cat ./src-tauri/Cargo.toml - - generate_build_version() { - ### Examble - ### input 0.5.6 output will be 0.5.6 and 0.5.6.0 - ### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2 - ### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213 - local new_version="$1" - local base_version - local t_value - - # Check if it has a "-" - if [[ "$new_version" == *-* ]]; then - base_version="${new_version%%-*}" # part before - - suffix="${new_version#*-}" # part after - - - # Check if it is rcX-beta - if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then - t_value="${BASH_REMATCH[1]}" - else - t_value="$suffix" - fi - else - base_version="$new_version" - t_value="0" - fi - - # Export two values - new_base_version="$base_version" - new_build_version="${base_version}.${t_value}" - } - generate_build_version ${{ inputs.new_version }} - sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template - sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template - - # Change app name for beta and nightly builds - if [ "${{ inputs.channel }}" != "stable" ]; then - jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json - mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json - - chmod +x .github/scripts/rename-tauri-app.sh - .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} - - echo "---------tauri.conf.json---------" - cat ./src-tauri/tauri.conf.json - - # Update Cargo.toml - ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" - ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" - echo "------------------" - cat ./src-tauri/Cargo.toml - - chmod +x .github/scripts/rename-workspace.sh - .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} - cat ./package.json - - sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template - sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template - fi - echo "---------nsis.template---------" - cat ./src-tauri/tauri.bundle.windows.nsis.template - - - name: Install AzureSignTool - run: | - dotnet tool install --global --version 6.0.0 AzureSignTool - - - name: Build app - shell: bash - run: | - make build-tauri - 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: ${{ secrets.AZURE_CERT_NAME }} - 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 }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} - - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: jan-windows-${{ inputs.new_version }} - path: | - ./src-tauri/target/release/bundle/nsis/*.exe - - ## create zip file and latest.yml for windows electron auto updater - - name: create zip file and latest.yml for windows electron auto updater - shell: bash - run: | - cd ./src-tauri/target/release/bundle/nsis - if [ "${{ inputs.channel }}" != "stable" ]; then - FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe - WIN_SIG=$(cat Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe.sig) - else - FILE_NAME=Jan_${{ inputs.new_version }}_x64-setup.exe - WIN_SIG=$(cat Jan_${{ inputs.new_version }}_x64-setup.exe.sig) - fi - - FILE_SIZE=$(stat -c %s $FILE_NAME) - echo "size: $FILE_SIZE" - - SH512_CHECKSUM=$(python3 ../../../../../.github/scripts/electron-checksum.py $FILE_NAME) - echo "sha512: $SH512_CHECKSUM" - CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") - echo "releaseDate: $CURRENT_TIME" - - # Create latest.yml file - echo "version: ${{ inputs.new_version }}" > latest.yml - echo "files:" >> latest.yml - echo " - url: $FILE_NAME" >> latest.yml - echo " sha512: $SH512_CHECKSUM" >> latest.yml - echo " size: $FILE_NAME" >> latest.yml - echo "path: $FILE_NAME" >> latest.yml - echo "sha512: $SH512_CHECKSUM" >> latest.yml - echo "releaseDate: $CURRENT_TIME" >> latest.yml - - cat latest.yml - cp latest.yml beta.yml - - echo "::set-output name=WIN_SIG::$WIN_SIG" - echo "::set-output name=FILE_NAME::$FILE_NAME" - id: metadata - - ## Upload to s3 for nightly and beta - - name: upload to aws s3 if public provider is aws - shell: bash - if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' - run: | - cd ./src-tauri/target/release/bundle/nsis - - # Upload for electron updater for nightly - aws s3 cp ./latest.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest.yml - aws s3 cp ./beta.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta.yml - - # Upload for tauri updater - aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }} - aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }}.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}.sig - 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' - - ## Upload to github release for stable release - - name: Upload release assert if public provider is github - if: inputs.channel == 'stable' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/nsis/latest.yml - asset_name: latest.yml - asset_content_type: text/yaml - - - name: Upload release asset if public provider is github - if: inputs.channel == 'beta' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/nsis/beta.yml - asset_name: beta.yml - asset_content_type: text/yaml - - - name: Upload release assert if public provider is github - if: inputs.public_provider == 'github' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/upload-release-asset@v1.0.1 - with: - upload_url: ${{ inputs.upload_url }} - asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }} - asset_name: ${{ steps.metadata.outputs.FILE_NAME }} - asset_content_type: application/octet-stream +name: tauri-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: '' + cortex_api_port: + required: false + type: string + default: '' + upload_url: + required: false + type: string + default: '' + channel: + required: true + type: string + default: 'nightly' + description: 'The channel to use for this job' + 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 + TAURI_SIGNING_PRIVATE_KEY: + required: false + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: + required: false + TAURI_SIGNING_PUBLIC_KEY: + required: false + outputs: + WIN_SIG: + value: ${{ jobs.build-windows-x64.outputs.WIN_SIG }} + FILE_NAME: + value: ${{ jobs.build-windows-x64.outputs.FILE_NAME }} + +jobs: + build-windows-x64: + runs-on: windows-latest + outputs: + WIN_SIG: ${{ steps.metadata.outputs.WIN_SIG }} + FILE_NAME: ${{ steps.metadata.outputs.FILE_NAME }} + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + with: + ref: ${{ inputs.ref }} + + - name: Replace Icons for Beta Build + if: inputs.channel != 'stable' + shell: bash + run: | + cp .github/scripts/icon-${{ inputs.channel }}.png src-tauri/icons/icon.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: Install ctoml + run: | + cargo install ctoml + + - name: Update app version base on tag + id: version_update + shell: bash + run: | + echo "Version: ${{ inputs.new_version }}" + # Update tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version | .bundle.createUpdaterArtifacts = true | .bundle.windows.nsis.template = "tauri.bundle.windows.nsis.template"' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + jq --arg version "${{ inputs.new_version }}" '.version = $version' web-app/package.json > /tmp/package.json + mv /tmp/package.json web-app/package.json + + ctoml ./src-tauri/Cargo.toml package.version "${{ inputs.new_version }}" + echo "---------Cargo.toml---------" + cat ./src-tauri/Cargo.toml + + generate_build_version() { + ### Examble + ### input 0.5.6 output will be 0.5.6 and 0.5.6.0 + ### input 0.5.6-rc2-beta output will be 0.5.6 and 0.5.6.2 + ### input 0.5.6-1213 output will be 0.5.6 and and 0.5.6.1213 + local new_version="$1" + local base_version + local t_value + + # Check if it has a "-" + if [[ "$new_version" == *-* ]]; then + base_version="${new_version%%-*}" # part before - + suffix="${new_version#*-}" # part after - + + # Check if it is rcX-beta + if [[ "$suffix" =~ ^rc([0-9]+)-beta$ ]]; then + t_value="${BASH_REMATCH[1]}" + else + t_value="$suffix" + fi + else + base_version="$new_version" + t_value="0" + fi + + # Export two values + new_base_version="$base_version" + new_build_version="${base_version}.${t_value}" + } + generate_build_version ${{ inputs.new_version }} + sed -i "s/jan_version/$new_base_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_build/$new_build_version/g" ./src-tauri/tauri.bundle.windows.nsis.template + + # Change app name for beta and nightly builds + if [ "${{ inputs.channel }}" != "stable" ]; then + jq '.plugins.updater.endpoints = ["https://delta.jan.ai/${{ inputs.channel }}/latest.json"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json + mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json + + chmod +x .github/scripts/rename-tauri-app.sh + .github/scripts/rename-tauri-app.sh ./src-tauri/tauri.conf.json ${{ inputs.channel }} + + echo "---------tauri.conf.json---------" + cat ./src-tauri/tauri.conf.json + + # Update Cargo.toml + ctoml ./src-tauri/Cargo.toml package.name "Jan-${{ inputs.channel }}" + ctoml ./src-tauri/Cargo.toml dependencies.tauri.features[] "devtools" + echo "------------------" + cat ./src-tauri/Cargo.toml + + chmod +x .github/scripts/rename-workspace.sh + .github/scripts/rename-workspace.sh ./package.json ${{ inputs.channel }} + cat ./package.json + + sed -i "s/jan_productname/Jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + sed -i "s/jan_mainbinaryname/jan-${{ inputs.channel }}/g" ./src-tauri/tauri.bundle.windows.nsis.template + fi + echo "---------nsis.template---------" + cat ./src-tauri/tauri.bundle.windows.nsis.template + + - name: Install AzureSignTool + run: | + dotnet tool install --global --version 6.0.0 AzureSignTool + + - name: Build app + shell: bash + run: | + curl -L -o ./src-tauri/binaries/vcomp140.dll https://catalog.jan.ai/vcomp140.dll + curl -L -o ./src-tauri/binaries/msvcp140_codecvt_ids.dll https://catalog.jan.ai/msvcp140_codecvt_ids.dll + ls ./src-tauri/binaries + make build-tauri + 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: ${{ secrets.AZURE_CERT_NAME }} + 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 }} + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + TAURI_SIGNING_PUBLIC_KEY: ${{ secrets.TAURI_SIGNING_PUBLIC_KEY }} + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: jan-windows-${{ inputs.new_version }} + path: | + ./src-tauri/target/release/bundle/nsis/*.exe + + ## create zip file and latest.yml for windows electron auto updater + - name: create zip file and latest.yml for windows electron auto updater + shell: bash + run: | + cd ./src-tauri/target/release/bundle/nsis + if [ "${{ inputs.channel }}" != "stable" ]; then + FILE_NAME=Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan-${{ inputs.channel }}_${{ inputs.new_version }}_x64-setup.exe.sig) + else + FILE_NAME=Jan_${{ inputs.new_version }}_x64-setup.exe + WIN_SIG=$(cat Jan_${{ inputs.new_version }}_x64-setup.exe.sig) + fi + + FILE_SIZE=$(stat -c %s $FILE_NAME) + echo "size: $FILE_SIZE" + + SH512_CHECKSUM=$(python3 ../../../../../.github/scripts/electron-checksum.py $FILE_NAME) + echo "sha512: $SH512_CHECKSUM" + CURRENT_TIME=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ") + echo "releaseDate: $CURRENT_TIME" + + # Create latest.yml file + echo "version: ${{ inputs.new_version }}" > latest.yml + echo "files:" >> latest.yml + echo " - url: $FILE_NAME" >> latest.yml + echo " sha512: $SH512_CHECKSUM" >> latest.yml + echo " size: $FILE_SIZE" >> latest.yml + echo "path: $FILE_NAME" >> latest.yml + echo "sha512: $SH512_CHECKSUM" >> latest.yml + echo "releaseDate: $CURRENT_TIME" >> latest.yml + + cat latest.yml + cp latest.yml beta.yml + + echo "::set-output name=WIN_SIG::$WIN_SIG" + echo "::set-output name=FILE_NAME::$FILE_NAME" + id: metadata + + ## Upload to s3 for nightly and beta + - name: upload to aws s3 if public provider is aws + shell: bash + if: inputs.public_provider == 'aws-s3' || inputs.channel == 'beta' + run: | + cd ./src-tauri/target/release/bundle/nsis + + # Upload for electron updater for nightly + aws s3 cp ./latest.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/latest.yml + aws s3 cp ./beta.yml s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/beta.yml + + # Upload for tauri updater + aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }} s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }} + aws s3 cp ./${{ steps.metadata.outputs.FILE_NAME }}.sig s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-${{ inputs.channel }}/${{ steps.metadata.outputs.FILE_NAME }}.sig + 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' + + ## Upload to github release for stable release + - name: Upload release assert if public provider is github + if: inputs.channel == 'stable' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/latest.yml + asset_name: latest.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.channel == 'beta' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/beta.yml + asset_name: beta.yml + asset_content_type: text/yaml + + - name: Upload release assert if public provider is github + if: inputs.public_provider == 'github' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions/upload-release-asset@v1.0.1 + with: + upload_url: ${{ inputs.upload_url }} + asset_path: ./src-tauri/target/release/bundle/nsis/${{ steps.metadata.outputs.FILE_NAME }} + asset_name: ${{ steps.metadata.outputs.FILE_NAME }} + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore index ad0612fba..df59e4644 100644 --- a/.gitignore +++ b/.gitignore @@ -22,19 +22,6 @@ coverage *.log core/lib/** -# Nitro binary files -extensions/*-extension/bin/*/nitro -extensions/*-extension/bin/*/*.metal -extensions/*-extension/bin/*/*.exe -extensions/*-extension/bin/*/*.dll -extensions/*-extension/bin/*/*.exp -extensions/*-extension/bin/*/*.lib -extensions/*-extension/bin/saved-* -extensions/*-extension/bin/*.tar.gz -extensions/*-extension/bin/vulkaninfoSDK.exe -extensions/*-extension/bin/vulkaninfo - - # Turborepo .turbo electron/test-data @@ -50,3 +37,11 @@ electron/shared/** # docs docs/yarn.lock electron/.version.bak +src-tauri/binaries/engines/cortex.llamacpp +src-tauri/resources/themes +src-tauri/resources/lib +src-tauri/Cargo.lock +src-tauri/icons +!src-tauri/icons/icon.png +src-tauri/gen/apple +src-tauri/resources/bin diff --git a/Makefile b/Makefile index 0c2a2ef8a..56b50a9d2 100644 --- a/Makefile +++ b/Makefile @@ -24,95 +24,44 @@ ifeq ($(OS),Windows_NT) echo "skip" endif yarn install - yarn build:joi yarn build:core - yarn build:server yarn build:extensions -check-file-counts: install-and-build -ifeq ($(OS),Windows_NT) - powershell -Command "if ((Get-ChildItem -Path pre-install -Filter *.tgz | Measure-Object | Select-Object -ExpandProperty Count) -ne (Get-ChildItem -Path extensions -Directory | Where-Object Name -like *-extension* | Measure-Object | Select-Object -ExpandProperty Count)) { Write-Host 'Number of .tgz files in pre-install does not match the number of subdirectories in extensions with package.json'; exit 1 } else { Write-Host 'Extension build successful' }" -else - @tgz_count=$$(find pre-install -type f -name "*.tgz" | wc -l); dir_count=$$(find extensions -mindepth 1 -maxdepth 1 -type d -exec test -e '{}/package.json' \; -print | wc -l); if [ $$tgz_count -ne $$dir_count ]; then echo "Number of .tgz files in pre-install ($$tgz_count) does not match the number of subdirectories in extension ($$dir_count)"; exit 1; else echo "Extension build successful"; fi -endif - -dev: check-file-counts +dev: install-and-build + yarn install:cortex + yarn download:bin + 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: check-file-counts +lint: install-and-build yarn lint -update-playwright-config: -ifeq ($(OS),Windows_NT) - echo -e "const RPconfig = {\n\ - apiKey: '$(REPORT_PORTAL_API_KEY)',\n\ - endpoint: '$(REPORT_PORTAL_URL)',\n\ - project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\ - launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\ - attributes: [\n\ - {\n\ - key: 'key',\n\ - value: 'value',\n\ - },\n\ - {\n\ - value: 'value',\n\ - },\n\ - ],\n\ - description: '$(REPORT_PORTAL_DESCRIPTION)',\n\ - }\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts; - sed -i "s/^ reporter: .*/ reporter: [['@reportportal\/agent-js-playwright', RPconfig]],/" electron/playwright.config.ts - -else ifeq ($(shell uname -s),Linux) - echo "const RPconfig = {\n\ - apiKey: '$(REPORT_PORTAL_API_KEY)',\n\ - endpoint: '$(REPORT_PORTAL_URL)',\n\ - project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\ - launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\ - attributes: [\n\ - {\n\ - key: 'key',\n\ - value: 'value',\n\ - },\n\ - {\n\ - value: 'value',\n\ - },\n\ - ],\n\ - description: '$(REPORT_PORTAL_DESCRIPTION)',\n\ - }\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts; - sed -i "s/^ reporter: .*/ reporter: [['@reportportal\/agent-js-playwright', RPconfig]],/" electron/playwright.config.ts -else - echo "const RPconfig = {\n\ - apiKey: '$(REPORT_PORTAL_API_KEY)',\n\ - endpoint: '$(REPORT_PORTAL_URL)',\n\ - project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\ - launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\ - attributes: [\n\ - {\n\ - key: 'key',\n\ - value: 'value',\n\ - },\n\ - {\n\ - value: 'value',\n\ - },\n\ - ],\n\ - description: '$(REPORT_PORTAL_DESCRIPTION)',\n\ - }\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts; - sed -i '' "s|^ reporter: .*| reporter: [['@reportportal\/agent-js-playwright', RPconfig]],|" electron/playwright.config.ts -endif - # Testing test: lint - yarn build:test - yarn test:coverage + # yarn build:test + # yarn test:coverage + # Need e2e setup for tauri backend yarn test # Builds and publishes the app -build-and-publish: check-file-counts - yarn build:publish +build-and-publish: install-and-build + yarn build # Build -build: check-file-counts +build: install-and-build + yarn build + +# Deprecated soon +build-tauri: install-and-build + yarn copy:lib yarn build clean: @@ -122,6 +71,8 @@ ifeq ($(OS),Windows_NT) -powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz" -powershell -Command "Remove-Item -Recurse -Force ./extensions/*/*.tgz" -powershell -Command "Remove-Item -Recurse -Force ./electron/pre-install/*.tgz" + -powershell -Command "Remove-Item -Recurse -Force ./src-tauri/resources" + -powershell -Command "Remove-Item -Recurse -Force ./src-tauri/target" -powershell -Command "if (Test-Path \"$($env:USERPROFILE)\jan\extensions\") { Remove-Item -Path \"$($env:USERPROFILE)\jan\extensions\" -Recurse -Force }" else ifeq ($(shell uname -s),Linux) find . -name "node_modules" -type d -prune -exec rm -rf '{}' + @@ -136,6 +87,8 @@ else ifeq ($(shell uname -s),Linux) rm -rf ./pre-install/*.tgz rm -rf ./extensions/*/*.tgz rm -rf ./electron/pre-install/*.tgz + rm -rf ./src-tauri/resources + rm -rf ./src-tauri/target rm -rf "~/jan/extensions" rm -rf "~/.cache/jan*" else @@ -150,6 +103,8 @@ else rm -rf ./pre-install/*.tgz rm -rf ./extensions/*/*.tgz rm -rf ./electron/pre-install/*.tgz + rm -rf ./src-tauri/resources + rm -rf ./src-tauri/target rm -rf ~/jan/extensions rm -rf ~/Library/Caches/jan* endif diff --git a/core/package.json b/core/package.json index d63fedb98..1e6291375 100644 --- a/core/package.json +++ b/core/package.json @@ -8,31 +8,13 @@ ], "homepage": "https://jan.ai", "license": "AGPL-3.0", - "browser": "dist/index.js", "main": "dist/index.js", - "module": "dist/node/index.cjs.js", "typings": "dist/types/index.d.ts", "files": [ "dist", "types" ], "author": "Jan ", - "exports": { - ".": "./dist/index.js", - "./node": "./dist/node/index.cjs.js" - }, - "typesVersions": { - "*": { - ".": [ - "./dist/index.js.map", - "./dist/types/index.d.ts" - ], - "node": [ - "./dist/node/index.cjs.js.map", - "./dist/types/node/index.d.ts" - ] - } - }, "scripts": { "lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'", "test": "jest", diff --git a/core/src/browser/core.test.ts b/core/src/browser/core.test.ts index 117298eb6..6197da023 100644 --- a/core/src/browser/core.test.ts +++ b/core/src/browser/core.test.ts @@ -1,3 +1,6 @@ +/** + * @jest-environment jsdom + */ import { openExternalUrl } from './core' import { joinPath } from './core' import { openFileExplorer } from './core' @@ -25,7 +28,7 @@ describe('test core apis', () => { }, } const result = await joinPath(paths) - expect(globalThis.core.api.joinPath).toHaveBeenCalledWith(paths) + expect(globalThis.core.api.joinPath).toHaveBeenCalledWith({ args: paths }) expect(result).toBe('/path/one/path/two') }) @@ -37,7 +40,7 @@ describe('test core apis', () => { }, } const result = await openFileExplorer(path) - expect(globalThis.core.api.openFileExplorer).toHaveBeenCalledWith(path) + expect(globalThis.core.api.openFileExplorer).toHaveBeenCalledWith({ path }) expect(result).toBe('opened') }) @@ -51,20 +54,6 @@ describe('test core apis', () => { expect(globalThis.core.api.getJanDataFolderPath).toHaveBeenCalled() expect(result).toBe('/path/to/jan/data') }) - - it('should execute function on main process', async () => { - const extension = 'testExtension' - const method = 'testMethod' - const args = ['arg1', 'arg2'] - globalThis.core = { - api: { - invokeExtensionFunc: jest.fn().mockResolvedValue('result'), - }, - } - const result = await executeOnMain(extension, method, ...args) - expect(globalThis.core.api.invokeExtensionFunc).toHaveBeenCalledWith(extension, method, ...args) - expect(result).toBe('result') - }) }) describe('dirName - just a pass thru api', () => { diff --git a/core/src/browser/core.ts b/core/src/browser/core.ts index 43b5f9d48..3025ba963 100644 --- a/core/src/browser/core.ts +++ b/core/src/browser/core.ts @@ -13,8 +13,11 @@ const executeOnMain: (extension: string, method: string, ...args: any[]) => Prom extension, method, ...args -) => globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args) - +) => { + if ('electronAPI' in window && window.electronAPI) + return globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args) + return () => {} +} /** * Gets Jan's data folder path. @@ -29,15 +32,15 @@ const getJanDataFolderPath = (): Promise => globalThis.core.api?.getJanD * @returns {Promise} A promise that resolves when the file explorer is opened. */ const openFileExplorer: (path: string) => Promise = (path) => - globalThis.core.api?.openFileExplorer(path) + globalThis.core.api?.openFileExplorer({ path }) /** * Joins multiple paths together. * @param paths - The paths to join. * @returns {Promise} A promise that resolves with the joined path. */ -const joinPath: (paths: string[]) => Promise = (paths) => - globalThis.core.api?.joinPath(paths) +const joinPath: (args: string[]) => Promise = (args) => + globalThis.core.api?.joinPath({ args }) /** * Get dirname of a file path. diff --git a/core/src/browser/extension.test.ts b/core/src/browser/extension.test.ts index 879258876..b2a1d1e73 100644 --- a/core/src/browser/extension.test.ts +++ b/core/src/browser/extension.test.ts @@ -1,7 +1,5 @@ import { BaseExtension } from './extension' import { SettingComponentProps } from '../types' -import { getJanDataFolderPath, joinPath } from './core' -import { fs } from './fs' jest.mock('./core') jest.mock('./fs') @@ -90,18 +88,32 @@ describe('BaseExtension', () => { { key: 'setting2', controllerProps: { value: 'value2' } } as any, ] - ;(getJanDataFolderPath as jest.Mock).mockResolvedValue('/data') - ;(joinPath as jest.Mock).mockResolvedValue('/data/settings/TestExtension') - ;(fs.existsSync as jest.Mock).mockResolvedValue(false) - ;(fs.mkdir as jest.Mock).mockResolvedValue(undefined) - ;(fs.writeFileSync as jest.Mock).mockResolvedValue(undefined) + const localStorageMock = (() => { + let store: Record = {} + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value + }, + removeItem: (key: string) => { + delete store[key] + }, + clear: () => { + store = {} + }, + } + })() + + Object.defineProperty(global, 'localStorage', { + value: localStorageMock, + }) + const mock = jest.spyOn(localStorage, 'setItem') await baseExtension.registerSettings(settings) - expect(fs.mkdir).toHaveBeenCalledWith('/data/settings/TestExtension') - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/data/settings/TestExtension', - JSON.stringify(settings, null, 2) + expect(mock).toHaveBeenCalledWith( + 'TestExtension', + JSON.stringify(settings) ) }) @@ -125,17 +137,15 @@ describe('BaseExtension', () => { ] jest.spyOn(baseExtension, 'getSettings').mockResolvedValue(settings) - ;(getJanDataFolderPath as jest.Mock).mockResolvedValue('/data') - ;(joinPath as jest.Mock).mockResolvedValue('/data/settings/TestExtension/settings.json') - ;(fs.writeFileSync as jest.Mock).mockResolvedValue(undefined) + const mockSetItem = jest.spyOn(localStorage, 'setItem') await baseExtension.updateSettings([ { key: 'setting1', controllerProps: { value: 'newValue' } } as any, ]) - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/data/settings/TestExtension/settings.json', - JSON.stringify([{ key: 'setting1', controllerProps: { value: 'newValue' } }], null, 2) + expect(mockSetItem).toHaveBeenCalledWith( + 'TestExtension', + JSON.stringify([{ key: 'setting1', controllerProps: { value: 'newValue' } }]) ) }) }) diff --git a/core/src/browser/extension.ts b/core/src/browser/extension.ts index a050b9d59..01d16c988 100644 --- a/core/src/browser/extension.ts +++ b/core/src/browser/extension.ts @@ -1,7 +1,4 @@ -import { Model, ModelEvent, SettingComponentProps } from '../types' -import { getJanDataFolderPath, joinPath } from './core' -import { events } from './events' -import { fs } from './fs' +import { Model, SettingComponentProps } from '../types' import { ModelManager } from './models' export enum ExtensionTypeEnum { @@ -117,22 +114,14 @@ export abstract class BaseExtension implements ExtensionType { return } - const extensionSettingFolderPath = await joinPath([ - await getJanDataFolderPath(), - 'settings', - this.name, - ]) settings.forEach((setting) => { setting.extensionName = this.name }) try { - if (!(await fs.existsSync(extensionSettingFolderPath))) - await fs.mkdir(extensionSettingFolderPath) - const settingFilePath = await joinPath([extensionSettingFolderPath, this.settingFileName]) - + const oldSettingsJson = localStorage.getItem(this.name) // Persists new settings - if (await fs.existsSync(settingFilePath)) { - const oldSettings = JSON.parse(await fs.readFileSync(settingFilePath, 'utf-8')) + if (oldSettingsJson) { + const oldSettings = JSON.parse(oldSettingsJson) settings.forEach((setting) => { // Keep setting value if (setting.controllerProps && Array.isArray(oldSettings)) @@ -141,7 +130,7 @@ export abstract class BaseExtension implements ExtensionType { )?.controllerProps?.value }) } - await fs.writeFileSync(settingFilePath, JSON.stringify(settings, null, 2)) + localStorage.setItem(this.name, JSON.stringify(settings)) } catch (err) { console.error(err) } @@ -180,17 +169,10 @@ export abstract class BaseExtension implements ExtensionType { async getSettings(): Promise { if (!this.name) return [] - const settingPath = await joinPath([ - await getJanDataFolderPath(), - this.settingFolderName, - this.name, - this.settingFileName, - ]) - try { - if (!(await fs.existsSync(settingPath))) return [] - const content = await fs.readFileSync(settingPath, 'utf-8') - const settings: SettingComponentProps[] = JSON.parse(content) + const settingsString = localStorage.getItem(this.name) + if (!settingsString) return [] + const settings: SettingComponentProps[] = JSON.parse(settingsString) return settings } catch (err) { console.warn(err) @@ -220,19 +202,7 @@ export abstract class BaseExtension implements ExtensionType { if (!updatedSettings.length) updatedSettings = componentProps as SettingComponentProps[] - const settingFolder = await joinPath([ - await getJanDataFolderPath(), - this.settingFolderName, - this.name, - ]) - - if (!(await fs.existsSync(settingFolder))) { - await fs.mkdir(settingFolder) - } - - const settingPath = await joinPath([settingFolder, this.settingFileName]) - - await fs.writeFileSync(settingPath, JSON.stringify(updatedSettings, null, 2)) + localStorage.setItem(this.name, JSON.stringify(updatedSettings)) updatedSettings.forEach((setting) => { this.onSettingUpdate( diff --git a/core/src/browser/extensions/engines/AIEngine.ts b/core/src/browser/extensions/engines/AIEngine.ts index 2d1bdb3c2..4f96eb93a 100644 --- a/core/src/browser/extensions/engines/AIEngine.ts +++ b/core/src/browser/extensions/engines/AIEngine.ts @@ -31,21 +31,21 @@ export abstract class AIEngine extends BaseExtension { /** * Loads the model. */ - async loadModel(model: Model): Promise { - if (model.engine.toString() !== this.provider) return Promise.resolve() + async loadModel(model: Partial, abortController?: AbortController): Promise { + if (model?.engine?.toString() !== this.provider) return Promise.resolve() events.emit(ModelEvent.OnModelReady, model) return Promise.resolve() } /** * Stops the model. */ - async unloadModel(model?: Model): Promise { + async unloadModel(model?: Partial): Promise { if (model?.engine && model.engine.toString() !== this.provider) return Promise.resolve() events.emit(ModelEvent.OnModelStopped, model ?? {}) return Promise.resolve() } - /* + /** * Inference request */ inference(data: MessageRequest) {} diff --git a/core/src/browser/extensions/engines/EngineManager.ts b/core/src/browser/extensions/engines/EngineManager.ts index 90ce75ac5..729d9d900 100644 --- a/core/src/browser/extensions/engines/EngineManager.ts +++ b/core/src/browser/extensions/engines/EngineManager.ts @@ -1,4 +1,3 @@ -import { InferenceEngine } from '../../../types' import { AIEngine } from './AIEngine' /** @@ -6,6 +5,7 @@ import { AIEngine } from './AIEngine' */ export class EngineManager { public engines = new Map() + public controller: AbortController | null = null /** * Registers an engine. @@ -21,22 +21,6 @@ export class EngineManager { * @returns The engine, if found. */ get(provider: string): T | undefined { - // Backward compatible provider - // nitro is migrated to cortex - if ( - [ - InferenceEngine.nitro, - InferenceEngine.cortex, - InferenceEngine.cortex_llamacpp, - InferenceEngine.cortex_onnx, - InferenceEngine.cortex_tensorrtllm, - InferenceEngine.cortex_onnx, - ] - .map((e) => e.toString()) - .includes(provider) - ) - provider = InferenceEngine.cortex - return this.engines.get(provider) as T | undefined } diff --git a/core/src/browser/extensions/engines/LocalOAIEngine.ts b/core/src/browser/extensions/engines/LocalOAIEngine.ts index b54f8fbde..026c5b2fe 100644 --- a/core/src/browser/extensions/engines/LocalOAIEngine.ts +++ b/core/src/browser/extensions/engines/LocalOAIEngine.ts @@ -29,7 +29,7 @@ export abstract class LocalOAIEngine extends OAIEngine { /** * Load the model. */ - override async loadModel(model: Model & { file_path?: string }): Promise { + override async loadModel(model: Model & { file_path?: string }, abortController?: AbortController): Promise { if (model.engine.toString() !== this.provider) return const modelFolder = 'file_path' in model && model.file_path ? await dirName(model.file_path) : await this.getModelFilePath(model.id) const systemInfo = await systemInformation() diff --git a/core/src/browser/extensions/engines/OAIEngine.test.ts b/core/src/browser/extensions/engines/OAIEngine.test.ts index 66537d0be..0e985fd1b 100644 --- a/core/src/browser/extensions/engines/OAIEngine.test.ts +++ b/core/src/browser/extensions/engines/OAIEngine.test.ts @@ -12,11 +12,7 @@ import { ChatCompletionRole, ContentType, } from '../../../types' -import { requestInference } from './helpers/sse' -import { ulid } from 'ulidx' -jest.mock('./helpers/sse') -jest.mock('ulidx') jest.mock('../../events') class TestOAIEngine extends OAIEngine { @@ -48,79 +44,6 @@ describe('OAIEngine', () => { ) }) - it('should handle inference request', async () => { - const data: MessageRequest = { - model: { engine: 'test-provider', id: 'test-model' } as any, - threadId: 'test-thread', - type: MessageRequestType.Thread, - assistantId: 'test-assistant', - messages: [{ role: ChatCompletionRole.User, content: 'Hello' }], - } - - ;(ulid as jest.Mock).mockReturnValue('test-id') - ;(requestInference as jest.Mock).mockReturnValue({ - subscribe: ({ next, complete }: any) => { - next('test response') - complete() - }, - }) - - await engine.inference(data) - - expect(requestInference).toHaveBeenCalledWith( - 'http://test-inference-url', - expect.objectContaining({ model: 'test-model' }), - expect.any(Object), - expect.any(AbortController), - { Authorization: 'Bearer test-token' }, - undefined - ) - - expect(events.emit).toHaveBeenCalledWith( - MessageEvent.OnMessageResponse, - expect.objectContaining({ id: 'test-id' }) - ) - expect(events.emit).toHaveBeenCalledWith( - MessageEvent.OnMessageUpdate, - expect.objectContaining({ - content: [ - { - type: ContentType.Text, - text: { value: 'test response', annotations: [] }, - }, - ], - status: MessageStatus.Ready, - }) - ) - }) - - it('should handle inference error', async () => { - const data: MessageRequest = { - model: { engine: 'test-provider', id: 'test-model' } as any, - threadId: 'test-thread', - type: MessageRequestType.Thread, - assistantId: 'test-assistant', - messages: [{ role: ChatCompletionRole.User, content: 'Hello' }], - } - - ;(ulid as jest.Mock).mockReturnValue('test-id') - ;(requestInference as jest.Mock).mockReturnValue({ - subscribe: ({ error }: any) => { - error({ message: 'test error', code: 500 }) - }, - }) - - await engine.inference(data) - - expect(events.emit).toHaveBeenLastCalledWith( - MessageEvent.OnMessageUpdate, - expect.objectContaining({ - status: 'error', - error_code: 500, - }) - ) - }) - it('should stop inference', () => { engine.stopInference() expect(engine.isCancelled).toBe(true) diff --git a/core/src/browser/extensions/engines/OAIEngine.ts b/core/src/browser/extensions/engines/OAIEngine.ts index 61032357c..3502aa1f7 100644 --- a/core/src/browser/extensions/engines/OAIEngine.ts +++ b/core/src/browser/extensions/engines/OAIEngine.ts @@ -1,18 +1,9 @@ -import { requestInference } from './helpers/sse' -import { ulid } from 'ulidx' import { AIEngine } from './AIEngine' import { - ChatCompletionRole, - ContentType, InferenceEvent, MessageEvent, MessageRequest, - MessageRequestType, - MessageStatus, Model, - ModelInfo, - ThreadContent, - ThreadMessage, } from '../../../types' import { events } from '../../events' @@ -53,111 +44,6 @@ export abstract class OAIEngine extends AIEngine { */ override onUnload(): void {} - /* - * Inference request - */ - override async inference(data: MessageRequest) { - if (!data.model?.id) { - events.emit(MessageEvent.OnMessageResponse, { - status: MessageStatus.Error, - content: [ - { - type: ContentType.Text, - text: { - value: 'No model ID provided', - annotations: [], - }, - }, - ], - }) - return - } - - const timestamp = Date.now() / 1000 - const message: ThreadMessage = { - id: ulid(), - thread_id: data.threadId, - type: data.type, - assistant_id: data.assistantId, - role: ChatCompletionRole.Assistant, - content: [], - status: MessageStatus.Pending, - created_at: timestamp, - completed_at: timestamp, - object: 'thread.message', - } - - if (data.type !== MessageRequestType.Summary) { - events.emit(MessageEvent.OnMessageResponse, message) - } - - this.isCancelled = false - this.controller = new AbortController() - - const model: ModelInfo = { - ...(this.loadedModel ? this.loadedModel : {}), - ...data.model, - } - - const header = await this.headers() - let requestBody = { - messages: data.messages ?? [], - model: model.id, - stream: true, - ...model.parameters, - } - if (this.transformPayload) { - requestBody = this.transformPayload(requestBody) - } - - requestInference( - this.inferenceUrl, - requestBody, - model, - this.controller, - header, - this.transformResponse - ).subscribe({ - next: (content: any) => { - const messageContent: ThreadContent = { - type: ContentType.Text, - text: { - value: content.trim(), - annotations: [], - }, - } - message.content = [messageContent] - events.emit(MessageEvent.OnMessageUpdate, message) - }, - complete: async () => { - message.status = message.content.length - ? MessageStatus.Ready - : MessageStatus.Error - events.emit(MessageEvent.OnMessageUpdate, message) - }, - error: async (err: any) => { - if (this.isCancelled || message.content.length) { - message.status = MessageStatus.Stopped - events.emit(MessageEvent.OnMessageUpdate, message) - return - } - message.status = MessageStatus.Error - message.content[0] = { - type: ContentType.Text, - text: { - value: - typeof message === 'string' - ? err.message - : (JSON.stringify(err.message) ?? err.detail), - annotations: [], - }, - } - message.error_code = err.code - events.emit(MessageEvent.OnMessageUpdate, message) - }, - }) - } - /** * Stops the inference. */ diff --git a/core/src/browser/extensions/engines/helpers/sse.test.ts b/core/src/browser/extensions/engines/helpers/sse.test.ts deleted file mode 100644 index f8c2ac6b4..000000000 --- a/core/src/browser/extensions/engines/helpers/sse.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { lastValueFrom, Observable } from 'rxjs' -import { requestInference } from './sse' - -import { ReadableStream } from 'stream/web' -describe('requestInference', () => { - it('should send a request to the inference server and return an Observable', () => { - // Mock the fetch function - const mockFetch: any = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - choices: [{ message: { content: 'Generated response' } }], - }), - headers: new Headers(), - redirected: false, - status: 200, - statusText: 'OK', - // Add other required properties here - }) - ) - jest.spyOn(global, 'fetch').mockImplementation(mockFetch) - - // Define the test inputs - const inferenceUrl = 'https://inference-server.com' - const requestBody = { message: 'Hello' } - const model = { id: 'model-id', parameters: { stream: false } } - - // Call the function - const result = requestInference(inferenceUrl, requestBody, model) - - // Assert the expected behavior - expect(result).toBeInstanceOf(Observable) - expect(lastValueFrom(result)).resolves.toEqual('Generated response') - }) - - it('returns 401 error', () => { - // Mock the fetch function - const mockFetch: any = jest.fn(() => - Promise.resolve({ - ok: false, - json: () => - Promise.resolve({ - error: { message: 'Invalid API Key.', code: 'invalid_api_key' }, - }), - headers: new Headers(), - redirected: false, - status: 401, - statusText: 'invalid_api_key', - // Add other required properties here - }) - ) - jest.spyOn(global, 'fetch').mockImplementation(mockFetch) - - // Define the test inputs - const inferenceUrl = 'https://inference-server.com' - const requestBody = { message: 'Hello' } - const model = { id: 'model-id', parameters: { stream: false } } - - // Call the function - const result = requestInference(inferenceUrl, requestBody, model) - - // Assert the expected behavior - expect(result).toBeInstanceOf(Observable) - expect(lastValueFrom(result)).rejects.toEqual({ - message: 'Invalid API Key.', - code: 'invalid_api_key', - }) - }) -}) - -it('should handle a successful response with a transformResponse function', () => { - // Mock the fetch function - const mockFetch: any = jest.fn(() => - Promise.resolve({ - ok: true, - json: () => - Promise.resolve({ - choices: [{ message: { content: 'Generated response' } }], - }), - headers: new Headers(), - redirected: false, - status: 200, - statusText: 'OK', - }) - ) - jest.spyOn(global, 'fetch').mockImplementation(mockFetch) - - // Define the test inputs - const inferenceUrl = 'https://inference-server.com' - const requestBody = { message: 'Hello' } - const model = { id: 'model-id', parameters: { stream: false } } - const transformResponse = (data: any) => - data.choices[0].message.content.toUpperCase() - - // Call the function - const result = requestInference( - inferenceUrl, - requestBody, - model, - undefined, - undefined, - transformResponse - ) - - // Assert the expected behavior - expect(result).toBeInstanceOf(Observable) - expect(lastValueFrom(result)).resolves.toEqual('GENERATED RESPONSE') -}) - -it('should handle a successful response with streaming enabled', () => { - // Mock the fetch function - const mockFetch: any = jest.fn(() => - Promise.resolve({ - ok: true, - body: new ReadableStream({ - start(controller) { - controller.enqueue( - new TextEncoder().encode( - 'data: {"choices": [{"delta": {"content": "Streamed"}}]}' - ) - ) - controller.enqueue(new TextEncoder().encode('data: [DONE]')) - controller.close() - }, - }), - headers: new Headers(), - redirected: false, - status: 200, - statusText: 'OK', - }) - ) - jest.spyOn(global, 'fetch').mockImplementation(mockFetch) - - // Define the test inputs - const inferenceUrl = 'https://inference-server.com' - const requestBody = { message: 'Hello' } - const model = { id: 'model-id', parameters: { stream: true } } - - // Call the function - const result = requestInference(inferenceUrl, requestBody, model) - - // Assert the expected behavior - expect(result).toBeInstanceOf(Observable) - expect(lastValueFrom(result)).resolves.toEqual('Streamed') -}) diff --git a/core/src/browser/extensions/engines/helpers/sse.ts b/core/src/browser/extensions/engines/helpers/sse.ts deleted file mode 100644 index 5c63008ff..000000000 --- a/core/src/browser/extensions/engines/helpers/sse.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Observable } from 'rxjs' -import { ErrorCode, ModelRuntimeParams } from '../../../../types' -/** - * Sends a request to the inference server to generate a response based on the recent messages. - * @param recentMessages - An array of recent messages to use as context for the inference. - * @returns An Observable that emits the generated response as a string. - */ -export function requestInference( - inferenceUrl: string, - requestBody: any, - model: { - id: string - parameters?: ModelRuntimeParams - }, - controller?: AbortController, - headers?: HeadersInit, - transformResponse?: Function -): Observable { - return new Observable((subscriber) => { - fetch(inferenceUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', - 'Accept': model.parameters?.stream - ? 'text/event-stream' - : 'application/json', - ...headers, - }, - body: JSON.stringify(requestBody), - signal: controller?.signal, - }) - .then(async (response) => { - if (!response.ok) { - if (response.status === 401) { - throw { - code: ErrorCode.InvalidApiKey, - message: 'Invalid API Key.', - } - } - let data = await response.json() - try { - handleError(data) - } catch (err) { - subscriber.error(err) - return - } - } - // There could be overriden stream parameter in the model - // that is set in request body (transformed payload) - if ( - requestBody?.stream === false || - model.parameters?.stream === false - ) { - const data = await response.json() - try { - handleError(data) - } catch (err) { - subscriber.error(err) - return - } - if (transformResponse) { - subscriber.next(transformResponse(data)) - } else { - subscriber.next( - data.choices - ? data.choices[0]?.message?.content - : (data.content[0]?.text ?? '') - ) - } - } else { - const stream = response.body - const decoder = new TextDecoder('utf-8') - const reader = stream?.getReader() - let content = '' - - while (true && reader) { - const { done, value } = await reader.read() - if (done) { - break - } - const text = decoder.decode(value) - const lines = text.trim().split('\n') - let cachedLines = '' - for (const line of lines) { - try { - if (transformResponse) { - content += transformResponse(line) - subscriber.next(content ?? '') - } else { - const toParse = cachedLines + line - if (!line.includes('data: [DONE]')) { - const data = JSON.parse(toParse.replace('data: ', '')) - try { - handleError(data) - } catch (err) { - subscriber.error(err) - return - } - content += data.choices[0]?.delta?.content ?? '' - if (content.startsWith('assistant: ')) { - content = content.replace('assistant: ', '') - } - if (content !== '') subscriber.next(content) - } - } - } catch { - cachedLines = line - } - } - } - } - subscriber.complete() - }) - .catch((err) => subscriber.error(err)) - }) -} - -/** - * Handle error and normalize it to a common format. - * @param data - */ -const handleError = (data: any) => { - if ( - data.error || - data.message || - data.detail || - (Array.isArray(data) && data.length && data[0].error) - ) { - throw data.error ?? data[0]?.error ?? data - } -} diff --git a/core/src/browser/extensions/enginesManagement.ts b/core/src/browser/extensions/enginesManagement.ts index 6986e8f48..0dbb418f4 100644 --- a/core/src/browser/extensions/enginesManagement.ts +++ b/core/src/browser/extensions/enginesManagement.ts @@ -1,5 +1,4 @@ import { - InferenceEngine, Engines, EngineVariant, EngineReleased, @@ -28,7 +27,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @param name - Inference engine name. * @returns A Promise that resolves to an array of installed engine. */ - abstract getInstalledEngines(name: InferenceEngine): Promise + abstract getInstalledEngines(name: string): Promise /** * @param name - Inference engine name. @@ -37,7 +36,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to an array of latest released engine by version. */ abstract getReleasedEnginesByVersion( - name: InferenceEngine, + name: string, version: string, platform?: string ): Promise @@ -48,7 +47,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to an array of latest released engine. */ abstract getLatestReleasedEngine( - name: InferenceEngine, + name: string, platform?: string ): Promise @@ -74,7 +73,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to unintall of engine. */ abstract uninstallEngine( - name: InferenceEngine, + name: string, engineConfig: EngineConfig ): Promise<{ messages: string }> @@ -83,7 +82,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to an object of default engine. */ abstract getDefaultEngineVariant( - name: InferenceEngine + name: string ): Promise /** @@ -92,7 +91,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to set default engine. */ abstract setDefaultEngineVariant( - name: InferenceEngine, + name: string, engineConfig: EngineConfig ): Promise<{ messages: string }> @@ -100,7 +99,7 @@ export abstract class EngineManagementExtension extends BaseExtension { * @returns A Promise that resolves to update engine. */ abstract updateEngine( - name: InferenceEngine, + name: string, engineConfig?: EngineConfig ): Promise<{ messages: string }> @@ -112,5 +111,5 @@ export abstract class EngineManagementExtension extends BaseExtension { /** * @returns A Promise that resolves to an object of remote models list . */ - abstract getRemoteModels(name: InferenceEngine | string): Promise + abstract getRemoteModels(name: string): Promise } diff --git a/core/src/browser/extensions/hardwareManagement.ts b/core/src/browser/extensions/hardwareManagement.ts index 1f7c36287..5de3c9257 100644 --- a/core/src/browser/extensions/hardwareManagement.ts +++ b/core/src/browser/extensions/hardwareManagement.ts @@ -19,7 +19,7 @@ export abstract class HardwareManagementExtension extends BaseExtension { /** * @returns A Promise that resolves to an object of set active gpus. */ - abstract setAvtiveGpu(data: { gpus: number[] }): Promise<{ + abstract setActiveGpu(data: { gpus: number[] }): Promise<{ message: string activated_gpus: number[] }> diff --git a/core/src/browser/fs.test.ts b/core/src/browser/fs.test.ts index 21da54874..04e6fbe1c 100644 --- a/core/src/browser/fs.test.ts +++ b/core/src/browser/fs.test.ts @@ -36,31 +36,31 @@ describe('fs module', () => { it('should call readFileSync with correct arguments', () => { const args = ['path/to/file'] fs.readFileSync(...args) - expect(globalThis.core.api.readFileSync).toHaveBeenCalledWith(...args) + expect(globalThis.core.api.readFileSync).toHaveBeenCalledWith({ args }) }) it('should call existsSync with correct arguments', () => { const args = ['path/to/file'] fs.existsSync(...args) - expect(globalThis.core.api.existsSync).toHaveBeenCalledWith(...args) + expect(globalThis.core.api.existsSync).toHaveBeenCalledWith({ args }) }) it('should call readdirSync with correct arguments', () => { const args = ['path/to/directory'] fs.readdirSync(...args) - expect(globalThis.core.api.readdirSync).toHaveBeenCalledWith(...args) + expect(globalThis.core.api.readdirSync).toHaveBeenCalledWith({ args }) }) it('should call mkdir with correct arguments', () => { const args = ['path/to/directory'] fs.mkdir(...args) - expect(globalThis.core.api.mkdir).toHaveBeenCalledWith(...args) + expect(globalThis.core.api.mkdir).toHaveBeenCalledWith({ args }) }) it('should call rm with correct arguments', () => { const args = ['path/to/directory'] fs.rm(...args) - expect(globalThis.core.api.rm).toHaveBeenCalledWith(...args, { recursive: true, force: true }) + expect(globalThis.core.api.rm).toHaveBeenCalledWith({ args }) }) it('should call unlinkSync with correct arguments', () => { diff --git a/core/src/browser/fs.ts b/core/src/browser/fs.ts index 7aa5f4d92..0a05d4c56 100644 --- a/core/src/browser/fs.ts +++ b/core/src/browser/fs.ts @@ -4,7 +4,7 @@ import { FileStat } from '../types' * Writes data to a file at the specified path. * @returns {Promise} A Promise that resolves when the file is written successfully. */ -const writeFileSync = (...args: any[]) => globalThis.core.api?.writeFileSync(...args) +const writeFileSync = (...args: any[]) => globalThis.core.api?.writeFileSync({ args }) /** * Writes blob data to a file at the specified path. @@ -19,29 +19,29 @@ const writeBlob: (path: string, data: string) => Promise = (path, data) => * Reads the contents of a file at the specified path. * @returns {Promise} A Promise that resolves with the contents of the file. */ -const readFileSync = (...args: any[]) => globalThis.core.api?.readFileSync(...args) +const readFileSync = (...args: any[]) => globalThis.core.api?.readFileSync({ args }) /** * Check whether the file exists * @param {string} path * @returns {boolean} A boolean indicating whether the path is a file. */ -const existsSync = (...args: any[]) => globalThis.core.api?.existsSync(...args) +const existsSync = (...args: any[]) => globalThis.core.api?.existsSync({ args }) /** * List the directory files * @returns {Promise} A Promise that resolves with the contents of the directory. */ -const readdirSync = (...args: any[]) => globalThis.core.api?.readdirSync(...args) +const readdirSync = (...args: any[]) => globalThis.core.api?.readdirSync({ args }) /** * Creates a directory at the specified path. * @returns {Promise} A Promise that resolves when the directory is created successfully. */ -const mkdir = (...args: any[]) => globalThis.core.api?.mkdir(...args) +const mkdir = (...args: any[]) => globalThis.core.api?.mkdir({ args }) /** * Removes a directory at the specified path. * @returns {Promise} A Promise that resolves when the directory is removed successfully. */ -const rm = (...args: any[]) => globalThis.core.api?.rm(...args, { recursive: true, force: true }) +const rm = (...args: any[]) => globalThis.core.api?.rm({ args }) /** * Deletes a file from the local file system. @@ -80,10 +80,8 @@ const getGgufFiles: (paths: string[]) => Promise = (paths) => * @param outsideJanDataFolder - Whether the file is outside the Jan data folder. * @returns {Promise} - A promise that resolves with the file's stats. */ -const fileStat: (path: string, outsideJanDataFolder?: boolean) => Promise = ( - path, - outsideJanDataFolder -) => globalThis.core.api?.fileStat(path, outsideJanDataFolder) +const fileStat: (path: string) => Promise = (path) => + globalThis.core.api?.fileStat({ args: path }) // TODO: Export `dummy` fs functions automatically // Currently adding these manually diff --git a/core/src/browser/index.test.ts b/core/src/browser/index.test.ts index c8cabbb0b..fcdb635ff 100644 --- a/core/src/browser/index.test.ts +++ b/core/src/browser/index.test.ts @@ -3,7 +3,6 @@ import * as Events from './events' import * as FileSystem from './fs' import * as Extension from './extension' import * as Extensions from './extensions' -import * as Tools from './tools' import * as Models from './models' describe('Module Tests', () => { @@ -27,10 +26,6 @@ describe('Module Tests', () => { expect(Extensions).toBeDefined() }) - it('should export all base tools', () => { - expect(Tools).toBeDefined() - }) - it('should export all base tools', () => { expect(Models).toBeDefined() }) diff --git a/core/src/browser/index.ts b/core/src/browser/index.ts index a6ce187ca..5912d8c3b 100644 --- a/core/src/browser/index.ts +++ b/core/src/browser/index.ts @@ -28,12 +28,6 @@ export * from './extension' */ export * from './extensions' -/** - * Export all base tools. - * @module - */ -export * from './tools' - /** * Export all base models. * @module diff --git a/core/src/browser/models/manager.ts b/core/src/browser/models/manager.ts index d5afe83d5..2c4c8750d 100644 --- a/core/src/browser/models/manager.ts +++ b/core/src/browser/models/manager.ts @@ -38,10 +38,13 @@ export class ModelManager { return this.models.get(id) as T | undefined } + /** - * The instance of the tool manager. + * Shared instance of ExtensionManager. */ - static instance(): ModelManager { - return (window.core?.modelManager as ModelManager) ?? new ModelManager() + static instance() { + if (!window.core.modelManager) + window.core.modelManager = new ModelManager() + return window.core.modelManager as ModelManager } } diff --git a/core/src/browser/tools/index.test.ts b/core/src/browser/tools/index.test.ts deleted file mode 100644 index 8a24d3bb6..000000000 --- a/core/src/browser/tools/index.test.ts +++ /dev/null @@ -1,5 +0,0 @@ - - -it('should not throw any errors when imported', () => { - expect(() => require('./index')).not.toThrow(); -}) diff --git a/core/src/browser/tools/index.ts b/core/src/browser/tools/index.ts deleted file mode 100644 index 24cd12780..000000000 --- a/core/src/browser/tools/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './manager' -export * from './tool' diff --git a/core/src/browser/tools/manager.ts b/core/src/browser/tools/manager.ts deleted file mode 100644 index b323ad7ce..000000000 --- a/core/src/browser/tools/manager.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AssistantTool, MessageRequest } from '../../types' -import { InferenceTool } from './tool' - -/** - * Manages the registration and retrieval of inference tools. - */ -export class ToolManager { - public tools = new Map() - - /** - * Registers a tool. - * @param tool - The tool to register. - */ - register(tool: T) { - this.tools.set(tool.name, tool) - } - - /** - * Retrieves a tool by it's name. - * @param name - The name of the tool to retrieve. - * @returns The tool, if found. - */ - get(name: string): T | undefined { - return this.tools.get(name) as T | undefined - } - - /* - ** Process the message request with the tools. - */ - process(request: MessageRequest, tools: AssistantTool[]): Promise { - return tools.reduce((prevPromise, currentTool) => { - return prevPromise.then((prevResult) => { - return currentTool.enabled - ? this.get(currentTool.type)?.process(prevResult, currentTool) ?? - Promise.resolve(prevResult) - : Promise.resolve(prevResult) - }) - }, Promise.resolve(request)) - } - - /** - * The instance of the tool manager. - */ - static instance(): ToolManager { - return (window.core?.toolManager as ToolManager) ?? new ToolManager() - } -} diff --git a/core/src/browser/tools/tool.test.ts b/core/src/browser/tools/tool.test.ts deleted file mode 100644 index dcb478478..000000000 --- a/core/src/browser/tools/tool.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { ToolManager } from '../../browser/tools/manager' -import { InferenceTool } from '../../browser/tools/tool' -import { AssistantTool, MessageRequest } from '../../types' - -class MockInferenceTool implements InferenceTool { - name = 'mockTool' - process(request: MessageRequest, tool: AssistantTool): Promise { - return Promise.resolve(request) - } -} - -it('should register a tool', () => { - const manager = new ToolManager() - const tool = new MockInferenceTool() - manager.register(tool) - expect(manager.get(tool.name)).toBe(tool) -}) - -it('should retrieve a tool by its name', () => { - const manager = new ToolManager() - const tool = new MockInferenceTool() - manager.register(tool) - const retrievedTool = manager.get(tool.name) - expect(retrievedTool).toBe(tool) -}) - -it('should return undefined for a non-existent tool', () => { - const manager = new ToolManager() - const retrievedTool = manager.get('nonExistentTool') - expect(retrievedTool).toBeUndefined() -}) - -it('should process the message request with enabled tools', async () => { - const manager = new ToolManager() - const tool = new MockInferenceTool() - manager.register(tool) - - const request: MessageRequest = { message: 'test' } as any - const tools: AssistantTool[] = [{ type: 'mockTool', enabled: true }] as any - - const result = await manager.process(request, tools) - expect(result).toBe(request) -}) - -it('should skip processing for disabled tools', async () => { - const manager = new ToolManager() - const tool = new MockInferenceTool() - manager.register(tool) - - const request: MessageRequest = { message: 'test' } as any - const tools: AssistantTool[] = [{ type: 'mockTool', enabled: false }] as any - - const result = await manager.process(request, tools) - expect(result).toBe(request) -}) - -it('should throw an error when process is called without implementation', () => { - class TestTool extends InferenceTool { - name = 'testTool' - } - const tool = new TestTool() - expect(() => tool.process({} as MessageRequest)).toThrowError() -}) diff --git a/core/src/browser/tools/tool.ts b/core/src/browser/tools/tool.ts deleted file mode 100644 index 0fd342933..000000000 --- a/core/src/browser/tools/tool.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AssistantTool, MessageRequest } from '../../types' - -/** - * Represents a base inference tool. - */ -export abstract class InferenceTool { - abstract name: string - /* - ** Process a message request and return the processed message request. - */ - abstract process(request: MessageRequest, tool?: AssistantTool): Promise -} diff --git a/core/src/node/api/processors/app.ts b/core/src/node/api/processors/app.ts index d86e6265c..d35fd1fd6 100644 --- a/core/src/node/api/processors/app.ts +++ b/core/src/node/api/processors/app.ts @@ -8,6 +8,7 @@ import { normalizeFilePath, getJanDataFolderPath, } from '../../helper' +import { readdirSync, readFileSync } from 'fs' export class App implements Processor { observer?: Function @@ -25,8 +26,8 @@ export class App implements Processor { /** * Joins multiple paths together, respect to the current OS. */ - joinPath(args: any[]) { - return join(...args) + joinPath(args: any) { + return join(...('args' in args ? args.args : args)) } /** @@ -69,6 +70,9 @@ export class App implements Processor { writeLog(args) } + /** + * Get app configurations. + */ getAppConfigurations() { return appConfiguration() } diff --git a/core/src/node/api/processors/fs.ts b/core/src/node/api/processors/fs.ts index ada744d53..7bc5f1e20 100644 --- a/core/src/node/api/processors/fs.ts +++ b/core/src/node/api/processors/fs.ts @@ -21,18 +21,21 @@ export class FileSystem implements Processor { return import(FileSystem.moduleName).then((mdl) => mdl[route]( ...args.map((arg: any, index: number) => { - if(index !== 0) { + const arg0 = args[0] + if ('args' in arg0) arg = arg0.args + if (Array.isArray(arg)) arg = arg[0] + if (index !== 0) { return arg } if (index === 0 && typeof arg !== 'string') { throw new Error(`Invalid argument ${JSON.stringify(args)}`) } const path = - (arg.startsWith(`file:/`) || arg.startsWith(`file:\\`)) - ? join(getJanDataFolderPath(), normalizeFilePath(arg)) - : arg + arg.startsWith(`file:/`) || arg.startsWith(`file:\\`) + ? join(getJanDataFolderPath(), normalizeFilePath(arg)) + : arg - if(path.startsWith(`http://`) || path.startsWith(`https://`)) { + if (path.startsWith(`http://`) || path.startsWith(`https://`)) { return path } const absolutePath = resolve(path) @@ -88,5 +91,4 @@ export class FileSystem implements Processor { }) }) } - } diff --git a/core/src/node/extension/extension.ts b/core/src/node/extension/extension.ts index aea14f705..cd2bb0e06 100644 --- a/core/src/node/extension/extension.ts +++ b/core/src/node/extension/extension.ts @@ -94,8 +94,6 @@ export default class Extension { `Package ${this.origin} does not contain a valid manifest: ${error}` ) } - - return true } /** diff --git a/core/src/node/helper/config.ts b/core/src/node/helper/config.ts index 6fb28d01f..89955a2d6 100644 --- a/core/src/node/helper/config.ts +++ b/core/src/node/helper/config.ts @@ -18,9 +18,7 @@ export const getAppConfigurations = (): AppConfiguration => { if (!fs.existsSync(configurationFile)) { // create default app config if we don't have one - console.debug( - `App config not found, creating default config at ${configurationFile}` - ) + console.debug(`App config not found, creating default config at ${configurationFile}`) fs.writeFileSync(configurationFile, JSON.stringify(appDefaultConfiguration)) return appDefaultConfiguration } @@ -31,28 +29,23 @@ export const getAppConfigurations = (): AppConfiguration => { ) return appConfigurations } catch (err) { - console.error( - `Failed to read app config, return default config instead! Err: ${err}` - ) + console.error(`Failed to read app config, return default config instead! Err: ${err}`) return defaultAppConfig() } } const getConfigurationFilePath = () => join( - global.core?.appPath() || - process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME'], + global.core?.appPath() || process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME'], configurationFileName ) -export const updateAppConfiguration = ( +export const updateAppConfiguration = ({ + configuration, +}: { configuration: AppConfiguration -): Promise => { +}): Promise => { const configurationFile = getConfigurationFilePath() - console.debug( - 'updateAppConfiguration, configurationFile: ', - configurationFile - ) fs.writeFileSync(configurationFile, JSON.stringify(configuration)) return Promise.resolve() @@ -87,14 +80,11 @@ export const getJanExtensionsPath = (): string => { */ export const defaultAppConfig = (): AppConfiguration => { const { app } = require('electron') - const defaultJanDataFolder = join( - app?.getPath('userData') ?? os?.homedir() ?? '', - 'data' - ) + const defaultJanDataFolder = join(app?.getPath('userData') ?? os?.homedir() ?? '', 'data') return { data_folder: process.env.CI === 'e2e' - ? (process.env.APP_CONFIG_PATH ?? resolve('./test-data')) + ? process.env.APP_CONFIG_PATH ?? resolve('./test-data') : defaultJanDataFolder, quick_ask: false, } diff --git a/core/src/types/api/index.ts b/core/src/types/api/index.ts index 2f33b72e4..b9584725d 100644 --- a/core/src/types/api/index.ts +++ b/core/src/types/api/index.ts @@ -148,10 +148,7 @@ export const CoreRoutes = [ ] export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)] -export const APIEvents = [ - ...Object.values(AppEvent), - ...Object.values(DownloadEvent), -] +export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)] export type PayloadType = { messages: ChatCompletionMessage[] model: string diff --git a/core/src/types/engine/index.ts b/core/src/types/engine/index.ts index 9a6beeeff..c7112a8b7 100644 --- a/core/src/types/engine/index.ts +++ b/core/src/types/engine/index.ts @@ -1,7 +1,5 @@ -import { InferenceEngine } from '../../types' - export type Engines = { - [key in InferenceEngine]: (EngineVariant & EngineConfig)[] + [key: string]: (EngineVariant & EngineConfig)[] } export type EngineMetadata = { @@ -22,13 +20,13 @@ export type EngineMetadata = { } export type EngineVariant = { - engine: InferenceEngine + engine: string name: string version: string } export type DefaultEngineVariant = { - engine: InferenceEngine + engine: string variant: string version: string } diff --git a/core/src/types/inference/inferenceEntity.ts b/core/src/types/inference/inferenceEntity.ts index c37e3b079..ac2e48d32 100644 --- a/core/src/types/inference/inferenceEntity.ts +++ b/core/src/types/inference/inferenceEntity.ts @@ -7,6 +7,7 @@ export enum ChatCompletionRole { System = 'system', Assistant = 'assistant', User = 'user', + Tool = 'tool', } /** @@ -18,6 +19,9 @@ export type ChatCompletionMessage = { content?: ChatCompletionMessageContent /** The role of the author of this message. **/ role: ChatCompletionRole + type?: string + output?: string + tool_call_id?: string } export type ChatCompletionMessageContent = diff --git a/core/src/types/message/messageEntity.ts b/core/src/types/message/messageEntity.ts index edd253a57..20979c68e 100644 --- a/core/src/types/message/messageEntity.ts +++ b/core/src/types/message/messageEntity.ts @@ -36,6 +36,8 @@ export type ThreadMessage = { type?: string /** The error code which explain what error type. Used in conjunction with MessageStatus.Error */ error_code?: ErrorCode + + tool_call_id?: string } /** @@ -43,6 +45,9 @@ export type ThreadMessage = { * @data_transfer_object */ export type MessageRequest = { + /** + * The id of the message request. + */ id?: string /** @@ -71,6 +76,11 @@ export type MessageRequest = { // TODO: deprecate threadId field thread?: Thread + /** + * ChatCompletion tools + */ + tools?: MessageTool[] + /** Engine name to process */ engine?: string @@ -78,6 +88,24 @@ export type MessageRequest = { type?: string } +/** + * ChatCompletion Tool parameters + */ +export type MessageTool = { + type: string + function: MessageFunction +} + +/** + * ChatCompletion Tool's function parameters + */ +export type MessageFunction = { + name: string + description?: string + parameters?: Record + strict?: boolean +} + /** * The status of the message. * @data_transfer_object diff --git a/core/src/types/model/modelEntity.ts b/core/src/types/model/modelEntity.ts index 6e47c9ae4..83d8a864c 100644 --- a/core/src/types/model/modelEntity.ts +++ b/core/src/types/model/modelEntity.ts @@ -6,29 +6,7 @@ export type ModelInfo = { id: string settings?: ModelSettingParams parameters?: ModelRuntimeParams - engine?: InferenceEngine -} - -/** - * Represents the inference engine. - * @stored - */ -export enum InferenceEngine { - anthropic = 'anthropic', - mistral = 'mistral', - martian = 'martian', - openrouter = 'openrouter', - nitro = 'nitro', - openai = 'openai', - groq = 'groq', - triton_trtllm = 'triton_trtllm', - nitro_tensorrt_llm = 'nitro-tensorrt-llm', - cohere = 'cohere', - nvidia = 'nvidia', - cortex = 'cortex', - cortex_llamacpp = 'llama-cpp', - cortex_onnx = 'onnxruntime', - cortex_tensorrtllm = 'tensorrt-llm', + engine?: string } // Represents an artifact of a model, including its filename and URL @@ -105,7 +83,7 @@ export type Model = { /** * The model engine. */ - engine: InferenceEngine + engine: string } // Represents metadata associated with a model diff --git a/core/src/types/thread/threadEntity.ts b/core/src/types/thread/threadEntity.ts index dd88b10ec..ab61787e6 100644 --- a/core/src/types/thread/threadEntity.ts +++ b/core/src/types/thread/threadEntity.ts @@ -27,8 +27,8 @@ export type Thread = { * @stored */ export type ThreadAssistantInfo = { - assistant_id: string - assistant_name: string + id: string + name: string model: ModelInfo instructions?: string tools?: AssistantTool[] diff --git a/electron/.eslintrc.js b/electron/.eslintrc.js deleted file mode 100644 index 20e79804f..000000000 --- a/electron/.eslintrc.js +++ /dev/null @@ -1,46 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], - env: { - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react/recommended', - ], - rules: { - 'react/prop-types': 'off', // In favor of strong typing - no need to dedupe - 'react/no-is-mounted': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, - settings: { - react: { - createClass: 'createReactClass', // Regex for Component Factory to use, - // default to "createReactClass" - pragma: 'React', // Pragma to use, default to "React" - version: 'detect', // React version. "detect" automatically picks the version you have installed. - // You can also use `16.0`, `16.3`, etc, if you want to override the detected value. - // default to latest and warns if missing - // It will default to "detect" in the future - }, - linkComponents: [ - // Components used as alternatives to for linking, eg. - 'Hyperlink', - { name: 'Link', linkAttribute: 'to' }, - ], - }, - ignorePatterns: [ - 'build', - 'renderer', - 'node_modules', - '@global', - 'playwright-report', - 'test-data', - ], -} diff --git a/electron/@global/index.ts b/electron/@global/index.ts deleted file mode 100644 index b2d55fc1c..000000000 --- a/electron/@global/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export {} - -declare global { - namespace NodeJS { - interface Global { - core: any - } - } - var core: any | undefined -} diff --git a/electron/entitlements.mac.plist b/electron/entitlements.mac.plist deleted file mode 100644 index ad77a2a1e..000000000 --- a/electron/entitlements.mac.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.allow-dyld-environment-variables - - com.apple.security.cs.disable-library-validation - - - \ No newline at end of file diff --git a/electron/handlers/common.ts b/electron/handlers/common.ts deleted file mode 100644 index a2a1bd2f7..000000000 --- a/electron/handlers/common.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Handler, RequestHandler } from '@janhq/core/node' -import { ipcMain } from 'electron' -import { windowManager } from '../managers/window' - -export function injectHandler() { - const ipcWrapper: Handler = ( - route: string, - listener: (...args: any[]) => any - ) => - ipcMain.handle(route, async (_event, ...args: any[]) => { - return listener(...args) - }) - - const handler = new RequestHandler( - ipcWrapper, - (channel: string, args: any) => - windowManager.mainWindow?.webContents.send(channel, args) - ) - handler.handle() -} diff --git a/electron/handlers/native.ts b/electron/handlers/native.ts deleted file mode 100644 index f8f70c302..000000000 --- a/electron/handlers/native.ts +++ /dev/null @@ -1,327 +0,0 @@ -import { app, ipcMain, dialog, shell, nativeTheme } from 'electron' -import { autoUpdater } from 'electron-updater' -import { join } from 'path' -import { windowManager } from '../managers/window' -import { - ModuleManager, - getJanDataFolderPath, - getJanExtensionsPath, - init, - AppEvent, - NativeRoute, - SelectFileProp, -} from '@janhq/core/node' -import { SelectFileOption } from '@janhq/core' -import { menu } from '../utils/menu' -import { migrate } from '../utils/migration' -import { createUserSpace } from '../utils/path' -import { setupExtensions } from '../utils/extension' - -const isMac = process.platform === 'darwin' - -export function handleAppIPCs() { - /** - * Handles the "openAppDirectory" IPC message by opening the app's user data directory. - * The `shell.openPath` method is used to open the directory in the user's default file explorer. - * @param _event - The IPC event object. - */ - ipcMain.handle(NativeRoute.openAppDirectory, async (_event) => { - shell.openPath(getJanDataFolderPath()) - }) - - ipcMain.handle(NativeRoute.appUpdateDownload, async (_event) => { - autoUpdater.downloadUpdate() - }) - - /** - * Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light". - * This will change the appearance of the app to the light theme. - */ - ipcMain.handle(NativeRoute.setNativeThemeLight, () => { - nativeTheme.themeSource = 'light' - }) - - /** - * Handles the "setCloseApp" IPC message by closing the main application window. - * This effectively closes the application if no other windows are open. - */ - ipcMain.handle(NativeRoute.setCloseApp, () => { - windowManager.mainWindow?.close() - }) - - /** - * Handles the "setMinimizeApp" IPC message by minimizing the main application window. - * The window will be minimized to the system's taskbar or dock. - */ - ipcMain.handle(NativeRoute.setMinimizeApp, () => { - windowManager.mainWindow?.minimize() - }) - - /** - * Handles the "setMaximizeApp" IPC message. It toggles the maximization state of the main window. - * If the window is currently maximized, it will be un-maximized (restored to its previous size). - * If the window is not maximized, it will be maximized to fill the screen. - * @param _event - The IPC event object. - */ - ipcMain.handle(NativeRoute.setMaximizeApp, async (_event) => { - if (windowManager.mainWindow?.isMaximized()) { - windowManager.mainWindow.unmaximize() - } else { - windowManager.mainWindow?.maximize() - } - }) - - /** - * Handles the "setNativeThemeDark" IPC message by setting the native theme source to "dark". - * This will change the appearance of the app to the dark theme. - */ - ipcMain.handle(NativeRoute.setNativeThemeDark, () => { - nativeTheme.themeSource = 'dark' - }) - - /** - * Opens a URL in the user's default browser. - * @param _event - The IPC event object. - * @param url - The URL to open. - */ - ipcMain.handle(NativeRoute.openExternalUrl, async (_event, url) => { - shell.openExternal(url) - }) - - /** - * Opens a URL in the user's default browser. - * @param _event - The IPC event object. - * @param url - The URL to open. - */ - ipcMain.handle(NativeRoute.openFileExplore, async (_event, url) => { - shell.openPath(url) - }) - - /** - * Relaunches the app in production - reload window in development. - * @param _event - The IPC event object. - * @param url - The URL to reload. - */ - ipcMain.handle(NativeRoute.relaunch, async (_event) => { - ModuleManager.instance.clearImportedModules() - - if (app.isPackaged) { - app.relaunch() - app.exit() - } else { - for (const modulePath in ModuleManager.instance.requiredModules) { - delete require.cache[ - require.resolve(join(getJanExtensionsPath(), modulePath)) - ] - } - init({ - // Function to check from the main process that user wants to install a extension - confirmInstall: async (_extensions: string[]) => { - return true - }, - // Path to install extension to - extensionsPath: getJanExtensionsPath(), - }) - windowManager.mainWindow?.reload() - } - }) - - /** - * Handles the "selectDirectory" IPC message to open a dialog for selecting a directory. - * If no main window is found, logs an error and exits. - * @returns {string} The path of the selected directory, or nothing if canceled. - */ - ipcMain.handle(NativeRoute.selectDirectory, async () => { - const mainWindow = windowManager.mainWindow - if (!mainWindow) { - console.error('No main window found') - return - } - const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, { - title: 'Select a folder', - buttonLabel: 'Select Folder', - properties: ['openDirectory', 'createDirectory'], - }) - if (canceled) { - return - } else { - return filePaths[0] - } - }) - - /** - * Handles the "selectFiles" IPC message to open a dialog for selecting files. - * Allows options for setting the dialog title, button label, and selection properties. - * Logs an error if no main window is found. - * @param _event - The IPC event object. - * @param option - Options for customizing file selection dialog. - * @returns {string[]} An array of selected file paths, or nothing if canceled. - */ - ipcMain.handle( - NativeRoute.selectFiles, - async (_event, option?: SelectFileOption) => { - const mainWindow = windowManager.mainWindow - if (!mainWindow) { - console.error('No main window found') - return - } - - const title = option?.title ?? 'Select files' - const buttonLabel = option?.buttonLabel ?? 'Select' - const props: SelectFileProp[] = ['openFile'] - - if (option?.allowMultiple) { - props.push('multiSelections') - } - - if (option?.selectDirectory) { - props.push('openDirectory') - } - console.debug(`Select files with props: ${props}`) - const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, { - title, - buttonLabel, - properties: props, - filters: option?.filters, - }) - - if (canceled) return - - return filePaths - } - ) - - /** - * Handles the "hideQuickAskWindow" IPC message to hide the quick ask window. - * @returns A promise that resolves when the window is hidden. - */ - ipcMain.handle( - NativeRoute.hideQuickAskWindow, - async (): Promise => windowManager.hideQuickAskWindow() - ) - - /** - * Handles the "sendQuickAskInput" IPC message to send user input to the main window. - * @param _event - The IPC event object. - * @param input - User input string to be sent. - */ - ipcMain.handle( - NativeRoute.sendQuickAskInput, - async (_event, input: string): Promise => { - windowManager.mainWindow?.webContents.send( - AppEvent.onUserSubmitQuickAsk, - input - ) - } - ) - - /** - * Handles the "showOpenMenu" IPC message to show the context menu at given coordinates. - * Only applicable on non-Mac platforms. - * @param e - The event object. - * @param args - Contains coordinates where the menu should appear. - */ - ipcMain.handle(NativeRoute.showOpenMenu, function (e, args) { - if (!isMac && windowManager.mainWindow) { - menu.popup({ - window: windowManager.mainWindow, - x: args.x, - y: args.y, - }) - } - }) - - /** - * Handles the "hideMainWindow" IPC message to hide the main application window. - * @returns A promise that resolves when the window is hidden. - */ - ipcMain.handle( - NativeRoute.hideMainWindow, - async (): Promise => windowManager.hideMainWindow() - ) - - /** - * Handles the "showMainWindow" IPC message to show the main application window. - * @returns A promise that resolves when the window is shown. - */ - ipcMain.handle( - NativeRoute.showMainWindow, - async (): Promise => windowManager.showMainWindow() - ) - - /** - * Handles the "quickAskSizeUpdated" IPC message to update the size of the quick ask window. - * Resizes window by the given height offset. - * @param _event - The IPC event object. - * @param heightOffset - The amount of height to increase. - * @returns A promise that resolves when the window is resized. - */ - ipcMain.handle( - NativeRoute.quickAskSizeUpdated, - async (_event, heightOffset: number): Promise => - windowManager.expandQuickAskWindow(heightOffset) - ) - - /** - * Handles the "ackDeepLink" IPC message to acknowledge a deep link. - * Triggers handling of deep link in the application. - * @param _event - The IPC event object. - * @returns A promise that resolves when the deep link is acknowledged. - */ - ipcMain.handle(NativeRoute.ackDeepLink, async (_event): Promise => { - windowManager.ackDeepLink() - }) - - /** - * Handles the "factoryReset" IPC message to reset the application to its initial state. - * Clears loaded modules, recreates user space, runs migrations, and sets up extensions. - * @param _event - The IPC event object. - * @returns A promise that resolves after the reset operations are complete. - */ - ipcMain.handle(NativeRoute.factoryReset, async (_event): Promise => { - ModuleManager.instance.clearImportedModules() - return createUserSpace().then(migrate).then(setupExtensions) - }) - - /** - * Handles the "startServer" IPC message to start the Jan API server. - * Initializes and starts server with provided configuration options. - * @param _event - The IPC event object. - * @param args - Configuration object containing host, port, CORS settings etc. - * @returns Promise that resolves when server starts successfully - */ - ipcMain.handle( - NativeRoute.startServer, - async (_event, args): Promise => { - const { startServer } = require('@janhq/server') - return startServer({ - host: args?.host, - port: args?.port, - isCorsEnabled: args?.isCorsEnabled, - isVerboseEnabled: args?.isVerboseEnabled, - prefix: args?.prefix, - }) - } - ) - - /** - * Handles the "stopServer" IPC message to stop the Jan API server. - * Gracefully shuts down the server instance. - * @param _event - The IPC event object - * @returns Promise that resolves when server stops successfully - */ - ipcMain.handle(NativeRoute.stopServer, async (_event): Promise => { - /** - * Stop Jan API Server. - */ - const { stopServer } = require('@janhq/server') - return stopServer() - }) - - /** - * Handles the "appToken" IPC message to generate a random app ID. - */ - ipcMain.handle(NativeRoute.appToken, async (_event): Promise => { - return process.env.appToken ?? 'cortex.cpp' - }) -} diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts deleted file mode 100644 index 5dcbda582..000000000 --- a/electron/handlers/update.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { app, dialog } from 'electron' -import { windowManager } from './../managers/window' -import { - ProgressInfo, - UpdateDownloadedEvent, - UpdateInfo, - autoUpdater, -} from 'electron-updater' -import { AppEvent } from '@janhq/core/node' -import { trayManager } from '../managers/tray' - -export let waitingToInstallVersion: string | undefined = undefined - -export function handleAppUpdates() { - /* Should not check for update during development */ - if (!app.isPackaged) { - return - } - - /* New Update Available */ - autoUpdater.on('update-available', async (_info: UpdateInfo) => { - windowManager.mainWindow?.webContents.send( - AppEvent.onAppUpdateAvailable, - {} - ) - }) - - /* App Update Completion Message */ - autoUpdater.on('update-downloaded', async (_info: UpdateDownloadedEvent) => { - windowManager.mainWindow?.webContents.send( - AppEvent.onAppUpdateDownloadSuccess, - {} - ) - const action = await dialog.showMessageBox({ - message: `Update downloaded. Please restart the application to apply the updates.`, - buttons: ['Restart', 'Later'], - }) - if (action.response === 0) { - trayManager.destroyCurrentTray() - windowManager.closeQuickAskWindow() - waitingToInstallVersion = _info?.version - autoUpdater.quitAndInstall() - } - }) - - /* App Update Error */ - autoUpdater.on('error', (info: Error) => { - windowManager.mainWindow?.webContents.send( - AppEvent.onAppUpdateDownloadError, - { failedToInstallVersion: waitingToInstallVersion, info } - ) - }) - - /* App Update Progress */ - autoUpdater.on('download-progress', (progress: ProgressInfo) => { - console.debug('app update progress: ', progress.percent) - windowManager.mainWindow?.webContents.send( - AppEvent.onAppUpdateDownloadUpdate, - { - ...progress, - } - ) - }) - - autoUpdater.autoDownload = false - autoUpdater.autoInstallOnAppQuit = true - if (process.env.CI !== 'e2e') { - autoUpdater.checkForUpdates() - } -} diff --git a/electron/icons/icon-tray.png b/electron/icons/icon-tray.png deleted file mode 100644 index ab356a9dc..000000000 Binary files a/electron/icons/icon-tray.png and /dev/null differ diff --git a/electron/icons/icon-tray@2x.png b/electron/icons/icon-tray@2x.png deleted file mode 100644 index a82c285f5..000000000 Binary files a/electron/icons/icon-tray@2x.png and /dev/null differ diff --git a/electron/icons/icon.ico b/electron/icons/icon.ico deleted file mode 100644 index 5d18719e8..000000000 Binary files a/electron/icons/icon.ico and /dev/null differ diff --git a/electron/icons/icon.png b/electron/icons/icon.png deleted file mode 100644 index 289f99ded..000000000 Binary files a/electron/icons/icon.png and /dev/null differ diff --git a/electron/icons_dev/jan-beta-tray.png b/electron/icons_dev/jan-beta-tray.png deleted file mode 100644 index eaca9ad9a..000000000 Binary files a/electron/icons_dev/jan-beta-tray.png and /dev/null differ diff --git a/electron/icons_dev/jan-beta-tray@2x.png b/electron/icons_dev/jan-beta-tray@2x.png deleted file mode 100644 index deb83aace..000000000 Binary files a/electron/icons_dev/jan-beta-tray@2x.png and /dev/null differ diff --git a/electron/icons_dev/jan-beta.ico b/electron/icons_dev/jan-beta.ico deleted file mode 100644 index 85cf0c1b4..000000000 Binary files a/electron/icons_dev/jan-beta.ico and /dev/null differ diff --git a/electron/icons_dev/jan-beta.png b/electron/icons_dev/jan-beta.png deleted file mode 100644 index 4b715494d..000000000 Binary files a/electron/icons_dev/jan-beta.png and /dev/null differ diff --git a/electron/icons_dev/jan-nightly-tray.png b/electron/icons_dev/jan-nightly-tray.png deleted file mode 100644 index bf164a0a6..000000000 Binary files a/electron/icons_dev/jan-nightly-tray.png and /dev/null differ diff --git a/electron/icons_dev/jan-nightly-tray@2x.png b/electron/icons_dev/jan-nightly-tray@2x.png deleted file mode 100644 index 3cab5709d..000000000 Binary files a/electron/icons_dev/jan-nightly-tray@2x.png and /dev/null differ diff --git a/electron/icons_dev/jan-nightly.ico b/electron/icons_dev/jan-nightly.ico deleted file mode 100644 index 8e64ba8b1..000000000 Binary files a/electron/icons_dev/jan-nightly.ico and /dev/null differ diff --git a/electron/icons_dev/jan-nightly.png b/electron/icons_dev/jan-nightly.png deleted file mode 100644 index 23f532947..000000000 Binary files a/electron/icons_dev/jan-nightly.png and /dev/null differ diff --git a/electron/jest.config.js b/electron/jest.config.js deleted file mode 100644 index ec5968ccd..000000000 --- a/electron/jest.config.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - collectCoverageFrom: ['src/**/*.{ts,tsx}'], - modulePathIgnorePatterns: ['/tests'], - moduleNameMapper: { - '@/(.*)': '/src/$1', - }, - runner: './testRunner.js', - transform: { - '^.+\\.tsx?$': [ - 'ts-jest', - { - diagnostics: false, - }, - ], - }, -} diff --git a/electron/main.ts b/electron/main.ts deleted file mode 100644 index 59e72ca24..000000000 --- a/electron/main.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { app, BrowserWindow } from 'electron' - -import { join, resolve } from 'path' -/** - * Managers - **/ -import { windowManager } from './managers/window' -import { getAppConfigurations, log } from '@janhq/core/node' - -/** - * IPC Handlers - **/ -import { injectHandler } from './handlers/common' -import { handleAppUpdates } from './handlers/update' -import { handleAppIPCs } from './handlers/native' - -/** - * Utils - **/ -import { setupMenu } from './utils/menu' -import { createUserSpace } from './utils/path' -import { migrate } from './utils/migration' -import { cleanUpAndQuit } from './utils/clean' -import { setupExtensions } from './utils/extension' -import { setupCore } from './utils/setup' -import { setupReactDevTool } from './utils/dev' - -import { trayManager } from './managers/tray' -import { logSystemInfo } from './utils/system' -import { registerGlobalShortcuts } from './utils/shortcut' -import { registerLogger } from './utils/logger' -import { randomBytes } from 'crypto' - -const preloadPath = join(__dirname, 'preload.js') -const preloadQuickAskPath = join(__dirname, 'preload.quickask.js') -const rendererPath = join(__dirname, '..', 'renderer') -const quickAskPath = join(rendererPath, 'search.html') -const mainPath = join(rendererPath, 'index.html') - -const mainUrl = 'http://localhost:3000' -const quickAskUrl = `${mainUrl}/search` - -const gotTheLock = app.requestSingleInstanceLock() - -if (process.defaultApp) { - if (process.argv.length >= 2) { - app.setAsDefaultProtocolClient('jan', process.execPath, [ - resolve(process.argv[1]), - ]) - } -} else { - app.setAsDefaultProtocolClient('jan') -} - -const createMainWindow = () => { - const startUrl = app.isPackaged ? `file://${mainPath}` : mainUrl - windowManager.createMainWindow(preloadPath, startUrl) -} - -// Generate a random token for the app -// This token is used for authentication when making request to cortex.cpp server -process.env.appToken = randomBytes(16).toString('hex') - -app - .whenReady() - .then(() => { - if (!gotTheLock) { - app.quit() - throw new Error('Another instance of the app is already running') - } else { - app.on( - 'second-instance', - (_event, commandLine, _workingDirectory): void => { - if (process.platform === 'win32' || process.platform === 'linux') { - // this is for handling deeplink on windows and linux - // since those OS will emit second-instance instead of open-url - const url = commandLine.pop() - if (url) { - windowManager.sendMainAppDeepLink(url) - } - } - windowManager.showMainWindow() - } - ) - } - }) - .then(setupCore) - .then(createUserSpace) - .then(registerLogger) - .then(migrate) - .then(setupExtensions) - .then(setupMenu) - .then(handleIPCs) - .then(() => process.env.CI !== 'e2e' && createQuickAskWindow()) - .then(createMainWindow) - .then(handleAppUpdates) - .then(registerGlobalShortcuts) - .then(() => { - if (!app.isPackaged) { - setupReactDevTool() - windowManager.mainWindow?.webContents.openDevTools() - } - }) - .then(() => process.env.CI !== 'e2e' && trayManager.createSystemTray()) - .then(logSystemInfo) - .then(() => { - app.on('activate', () => { - if (!BrowserWindow.getAllWindows().length) { - createMainWindow() - } else { - windowManager.showMainWindow() - } - }) - }) - -app.on('open-url', (_event, url) => { - windowManager.sendMainAppDeepLink(url) -}) - -app.on('before-quit', function (_event) { - trayManager.destroyCurrentTray() -}) - -app.once('quit', () => { - cleanUpAndQuit() -}) - -app.once('window-all-closed', () => { - // Feature Toggle for Quick Ask - if ( - getAppConfigurations().quick_ask && - !windowManager.isQuickAskWindowDestroyed() - ) - return - cleanUpAndQuit() -}) - -function createQuickAskWindow() { - // Feature Toggle for Quick Ask - if (!getAppConfigurations().quick_ask) return - const startUrl = app.isPackaged ? `file://${quickAskPath}` : quickAskUrl - windowManager.createQuickAskWindow(preloadQuickAskPath, startUrl) -} - -/** - * Handles various IPC messages from the renderer process. - */ -function handleIPCs() { - // Inject core handlers for IPCs - injectHandler() - - // Handle native IPCs - handleAppIPCs() -} - -/* - ** Suppress Node error messages - */ -process.on('uncaughtException', function (err) { - log(`Error: ${err}`) -}) diff --git a/electron/managers/mainWindowConfig.ts b/electron/managers/mainWindowConfig.ts deleted file mode 100644 index 997d081c3..000000000 --- a/electron/managers/mainWindowConfig.ts +++ /dev/null @@ -1,21 +0,0 @@ -const DEFAULT_MIN_WIDTH = 400 -const DEFAULT_MIN_HEIGHT = 600 - -export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = { - skipTaskbar: false, - minWidth: DEFAULT_MIN_WIDTH, - minHeight: DEFAULT_MIN_HEIGHT, - show: true, - // we want to go frameless on windows and linux - transparent: process.platform === 'darwin', - frame: process.platform === 'darwin', - titleBarStyle: 'hiddenInset', - vibrancy: 'fullscreen-ui', - visualEffectState: 'active', - backgroundMaterial: 'acrylic', - autoHideMenuBar: true, - trafficLightPosition: { - x: 16, - y: 10, - }, -} diff --git a/electron/managers/quickAskWindowConfig.ts b/electron/managers/quickAskWindowConfig.ts deleted file mode 100644 index 93180dd07..000000000 --- a/electron/managers/quickAskWindowConfig.ts +++ /dev/null @@ -1,22 +0,0 @@ -const DEFAULT_WIDTH = 556 - -const DEFAULT_HEIGHT = 60 - -export const quickAskWindowConfig: Electron.BrowserWindowConstructorOptions = { - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, - skipTaskbar: true, - acceptFirstMouse: true, - hasShadow: true, - alwaysOnTop: true, - show: false, - fullscreenable: false, - resizable: false, - center: true, - movable: true, - maximizable: false, - focusable: true, - transparent: false, - frame: false, - type: 'panel', -} diff --git a/electron/managers/tray.ts b/electron/managers/tray.ts deleted file mode 100644 index b81b1e556..000000000 --- a/electron/managers/tray.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { join } from 'path' -import { Tray, app, Menu } from 'electron' -import { windowManager } from '../managers/window' -import { getAppConfigurations } from '@janhq/core/node' - -class TrayManager { - currentTray: Tray | undefined - - createSystemTray = () => { - // Feature Toggle for Quick Ask - if (!getAppConfigurations().quick_ask) return - - if (this.currentTray) { - return - } - const iconPath = join(app.getAppPath(), 'icons', 'icon-tray.png') - const tray = new Tray(iconPath) - tray.setToolTip(app.getName()) - - tray.on('click', () => { - windowManager.showQuickAskWindow() - }) - - // Add context menu for windows only - if (process.platform === 'win32') { - const contextMenu = Menu.buildFromTemplate([ - { - label: 'Open Jan', - type: 'normal', - click: () => windowManager.showMainWindow(), - }, - { - label: 'Open Quick Ask', - type: 'normal', - click: () => windowManager.showQuickAskWindow(), - }, - { label: 'Quit', type: 'normal', click: () => app.quit() }, - ]) - - tray.setContextMenu(contextMenu) - } - this.currentTray = tray - } - - destroyCurrentTray() { - this.currentTray?.destroy() - this.currentTray = undefined - } -} - -export const trayManager = new TrayManager() diff --git a/electron/managers/window.ts b/electron/managers/window.ts deleted file mode 100644 index dbb3a5101..000000000 --- a/electron/managers/window.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { BrowserWindow, app, shell } from 'electron' -import { quickAskWindowConfig } from './quickAskWindowConfig' -import { mainWindowConfig } from './mainWindowConfig' -import { getAppConfigurations, AppEvent } from '@janhq/core/node' -import { getBounds, saveBounds } from '../utils/setup' - -/** - * Manages the current window instance. - */ -// TODO: refactor this -let isAppQuitting = false - -class WindowManager { - public mainWindow?: BrowserWindow - private _quickAskWindow: BrowserWindow | undefined = undefined - private _quickAskWindowVisible = false - private _mainWindowVisible = false - - private deeplink: string | undefined - /** - * Creates a new window instance. - * @returns The created window instance. - */ - async createMainWindow(preloadPath: string, startUrl: string) { - const bounds = await getBounds() - - this.mainWindow = new BrowserWindow({ - ...mainWindowConfig, - width: bounds.width, - height: bounds.height, - show: false, - x: bounds.x, - y: bounds.y, - webPreferences: { - nodeIntegration: true, - preload: preloadPath, - webSecurity: false, - }, - }) - - if (process.platform === 'win32' || process.platform === 'linux') { - /// This is work around for windows deeplink. - /// second-instance event is not fired when app is not open, so the app - /// does not received the deeplink. - const commandLine = process.argv.slice(1) - if (commandLine.length > 0) { - const url = commandLine[0] - this.sendMainAppDeepLink(url) - } - } - - this.mainWindow.on('resized', () => { - saveBounds(this.mainWindow?.getBounds()) - }) - - this.mainWindow.on('moved', () => { - saveBounds(this.mainWindow?.getBounds()) - }) - - /* Load frontend app to the window */ - this.mainWindow.loadURL(startUrl) - - /* Open external links in the default browser */ - this.mainWindow.webContents.setWindowOpenHandler(({ url }) => { - shell.openExternal(url) - return { action: 'deny' } - }) - - app.on('before-quit', function () { - isAppQuitting = true - }) - - windowManager.mainWindow?.on('close', function (evt) { - // Feature Toggle for Quick Ask - if (!getAppConfigurations().quick_ask) return - - if (!isAppQuitting) { - evt.preventDefault() - windowManager.hideMainWindow() - } - }) - - windowManager.mainWindow?.on('ready-to-show', function () { - windowManager.mainWindow?.show() - }) - } - - createQuickAskWindow(preloadPath: string, startUrl: string): void { - this._quickAskWindow = new BrowserWindow({ - ...quickAskWindowConfig, - webPreferences: { - nodeIntegration: true, - preload: preloadPath, - webSecurity: false, - }, - }) - - this._quickAskWindow.loadURL(startUrl) - this._quickAskWindow.on('blur', () => { - this.hideQuickAskWindow() - }) - } - - isMainWindowVisible(): boolean { - return this._mainWindowVisible - } - - hideMainWindow(): void { - this.mainWindow?.hide() - this._mainWindowVisible = false - } - - showMainWindow(): void { - this.mainWindow?.show() - this._mainWindowVisible = true - } - - hideQuickAskWindow(): void { - this._quickAskWindow?.hide() - this._quickAskWindowVisible = false - } - - showQuickAskWindow(): void { - this._quickAskWindow?.show() - this._quickAskWindowVisible = true - } - - closeQuickAskWindow(): void { - if (this._quickAskWindow?.isDestroyed()) return - this._quickAskWindow?.close() - this._quickAskWindow?.destroy() - this._quickAskWindow = undefined - this._quickAskWindowVisible = false - } - - isQuickAskWindowVisible(): boolean { - return this._quickAskWindowVisible - } - - isQuickAskWindowDestroyed(): boolean { - return this._quickAskWindow?.isDestroyed() ?? true - } - - /** - * Expand the quick ask window - */ - expandQuickAskWindow(heightOffset: number): void { - const width = quickAskWindowConfig.width! - const height = quickAskWindowConfig.height! + heightOffset - this._quickAskWindow?.setMinimumSize(width, height) - this._quickAskWindow?.setSize(width, height, true) - } - - /** - * Send the selected text to the quick ask window. - */ - sendQuickAskSelectedText(selectedText: string): void { - this._quickAskWindow?.webContents.send( - AppEvent.onSelectedText, - selectedText - ) - } - - /** - * Try to send the deep link to the main app. - */ - sendMainAppDeepLink(url: string): void { - this.deeplink = url - const interval = setInterval(() => { - if (!this.deeplink) clearInterval(interval) - const mainWindow = this.mainWindow - if (mainWindow) { - mainWindow.webContents.send(AppEvent.onDeepLink, this.deeplink) - if (mainWindow.isMinimized()) mainWindow.restore() - mainWindow.focus() - } - }, 500) - } - - /** - * Send main view state to the main app. - */ - sendMainViewState(route: string) { - if (this.mainWindow && !this.mainWindow.isDestroyed()) { - this.mainWindow.webContents.send(AppEvent.onMainViewStateChange, route) - } - } - - /** - * Clean up all windows. - */ - cleanUp(): void { - if (!this.mainWindow?.isDestroyed()) { - this.mainWindow?.close() - this.mainWindow?.destroy() - this.mainWindow = undefined - this._mainWindowVisible = false - } - if (!this._quickAskWindow?.isDestroyed()) { - this._quickAskWindow?.close() - this._quickAskWindow?.destroy() - this._quickAskWindow = undefined - this._quickAskWindowVisible = false - } - } - - /** - * Acknowledges that the window has received a deep link. We can remove it. - */ - ackDeepLink() { - this.deeplink = undefined - } -} - -export const windowManager = new WindowManager() diff --git a/electron/merge-latest-ymls.js b/electron/merge-latest-ymls.js deleted file mode 100644 index ee8caf825..000000000 --- a/electron/merge-latest-ymls.js +++ /dev/null @@ -1,29 +0,0 @@ -const yaml = require('js-yaml') -const fs = require('fs') - -// get two file paths from arguments: -const [, , ...args] = process.argv -const file1 = args[0] -const file2 = args[1] -const file3 = args[2] - -// check that all arguments are present and throw error instead -if (!file1 || !file2 || !file3) { - throw new Error( - 'Please provide 3 file paths as arguments: path to file1, to file2 and destination path' - ) -} - -const doc1 = yaml.load(fs.readFileSync(file1, 'utf8')) -console.log('doc1: ', doc1) - -const doc2 = yaml.load(fs.readFileSync(file2, 'utf8')) -console.log('doc2: ', doc2) - -const merged = { ...doc1, ...doc2 } -merged.files.push(...doc1.files) - -console.log('merged', merged) - -const mergedYml = yaml.dump(merged) -fs.writeFileSync(file3, mergedYml, 'utf8') diff --git a/electron/package.json b/electron/package.json deleted file mode 100644 index 8b673114b..000000000 --- a/electron/package.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "name": "jan", - "version": "0.1.1740752217", - "main": "./build/main.js", - "author": "Jan ", - "license": "MIT", - "productName": "Jan", - "homepage": "https://github.com/menloresearch/jan/tree/main/electron", - "description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.", - "build": { - "appId": "jan.ai.app", - "productName": "Jan", - "files": [ - "renderer/**/*", - "build/**/*.{js,map}", - "pre-install", - "themes", - "scripts/**/*", - "icons/**/*", - "themes", - "shared" - ], - "asarUnpack": [ - "pre-install", - "themes", - "docs", - "scripts", - "icons", - "themes", - "shared" - ], - "publish": [ - { - "provider": "github", - "owner": "janhq", - "repo": "jan" - } - ], - "extends": null, - "mac": { - "type": "distribution", - "entitlements": "./entitlements.mac.plist", - "entitlementsInherit": "./entitlements.mac.plist", - "notarize": { - "teamId": "F8AH6NHVY5" - }, - "icon": "icons/icon.png" - }, - "linux": { - "target": [ - "deb" - ], - "category": "Utility", - "icon": "icons/" - }, - "win": { - "icon": "icons/icon.png", - "target": [ - "nsis" - ] - }, - "nsis": { - "oneClick": true, - "installerIcon": "icons/icon.ico", - "uninstallerIcon": "icons/icon.ico", - "include": "scripts/uninstaller.nsh", - "deleteAppDataOnUninstall": true - }, - "protocols": [ - { - "name": "Jan", - "schemes": [ - "jan" - ] - } - ], - "artifactName": "jan-${os}-${arch}-${version}.${ext}" - }, - "scripts": { - "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", - "test:e2e": "DEBUG=pw:browser xvfb-maybe -- playwright test --workers=1", - "copy:assets": "rimraf --glob \"./pre-install/*.tgz\" && cpx \"../pre-install/*.tgz\" \"./pre-install\"", - "version-patch": "run-script-os", - "version-patch:darwin:linux": "jq '.version' package.json | tr -d '\"' > .version.bak && jq --arg ver \"0.1.$(date +%s)\" '.version = $ver' package.json > package.tmp && mv package.tmp package.json", - "version-patch:win32": "node -e \"const fs=require('fs');const pkg=require('./package.json');const bak=pkg.version;fs.writeFileSync('.version.bak',bak);pkg.version='0.1.'+Math.floor(Date.now()/1000);fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\"", - "version-restore": "run-script-os", - "version-restore:darwin:linux": "jq --arg ver $(cat .version.bak) '.version = $ver' package.json > package.tmp && mv package.tmp package.json && rm .version.bak", - "version-restore:win32": "node -e \"const fs=require('fs');const pkg=require('./package.json');const bak=fs.readFileSync('.version.bak','utf8');pkg.version=bak;fs.writeFileSync('package.json',JSON.stringify(pkg,null,2));\"", - "dev:darwin:linux": "yarn copy:assets && tsc -p . && yarn version-patch && electron . && yarn version-restore", - "dev:windows": "yarn copy:assets && tsc -p . && electron .", - "dev": "run-script-os", - "compile": "tsc -p .", - "start": "electron .", - "build": "yarn copy:assets && run-script-os", - "build:test": "yarn copy:assets && run-script-os", - "build:test:darwin": "tsc -p . && electron-builder -p never -m --dir", - "build:test:win32": "tsc -p . && electron-builder -p never -w --dir", - "build:test:linux": "tsc -p . && electron-builder -p never -l --dir", - "build:darwin": "tsc -p . && electron-builder -p never -m --universal", - "build:win32": "tsc -p . && electron-builder -p never -w", - "build:linux": "tsc -p . && electron-builder -p never -l deb -l AppImage", - "build:publish": "yarn copy:assets && run-script-os", - "build:publish:darwin": "tsc -p . && electron-builder -p always -m --universal", - "build:publish:win32": "tsc -p . && electron-builder -p always -w", - "build:publish:linux": "tsc -p . && electron-builder -p always -l deb -l AppImage" - }, - "dependencies": { - "@alumna/reflect": "^1.1.3", - "@janhq/core": "link:../core", - "@janhq/server": "link:../server", - "@kirillvakalov/nut-tree__nut-js": "4.2.1-2", - "@npmcli/arborist": "^7.1.0", - "electron-store": "^8.1.0", - "electron-updater": "^6.1.7", - "fs-extra": "^11.2.0", - "pacote": "^21.0.0", - "request": "^2.88.2", - "request-progress": "^3.0.0", - "ulidx": "^2.3.0" - }, - "devDependencies": { - "@electron/notarize": "^2.5.0", - "@playwright/test": "^1.38.1", - "@reportportal/agent-js-playwright": "^5.1.7", - "@types/npmcli__arborist": "^5.6.4", - "@types/pacote": "^11.1.7", - "@types/request": "^2.48.12", - "@typescript-eslint/eslint-plugin": "^6.7.3", - "@typescript-eslint/parser": "^6.7.3", - "electron": "30.0.6", - "electron-builder": "^24.13.3", - "electron-builder-squirrel-windows": "^24.13.3", - "electron-devtools-installer": "^3.2.0", - "electron-playwright-helpers": "^1.6.0", - "eslint": "8.57.0", - "eslint-plugin-react": "^7.34.0", - "rimraf": "^5.0.5", - "run-script-os": "^1.1.6", - "typescript": "^5.3.3", - "xvfb-maybe": "^0.2.1" - }, - "installConfig": { - "hoistingLimits": "workspaces" - }, - "packageManager": "yarn@4.5.3" -} diff --git a/electron/playwright.config.ts b/electron/playwright.config.ts deleted file mode 100644 index 71f435f57..000000000 --- a/electron/playwright.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { PlaywrightTestConfig } from '@playwright/test' - -const config: PlaywrightTestConfig = { - testDir: './tests/e2e', - retries: 0, - globalTimeout: 350000, - use: { - screenshot: 'only-on-failure', - video: 'retain-on-failure', - trace: 'retain-on-failure', - }, - // reporter: [['html', { outputFolder: './playwright-report' }]], -} -export default config diff --git a/electron/pre-install/.gitkeep b/electron/pre-install/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/electron/preload.quickask.ts b/electron/preload.quickask.ts deleted file mode 100644 index 7c2cadeb6..000000000 --- a/electron/preload.quickask.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Exposes a set of APIs to the renderer process via the contextBridge object. - * @module preload - */ - -import { APIEvents, APIRoutes } from '@janhq/core/node' -import { contextBridge, ipcRenderer } from 'electron' - -const interfaces: { [key: string]: (...args: any[]) => any } = {} - -// Loop over each route in APIRoutes -APIRoutes.forEach((method) => { - // For each method, create a function on the interfaces object - // This function invokes the method on the ipcRenderer with any provided arguments - - interfaces[method] = (...args: any[]) => ipcRenderer.invoke(method, ...args) -}) - -// Loop over each method in APIEvents -APIEvents.forEach((method) => { - // For each method, create a function on the interfaces object - // This function sets up an event listener on the ipcRenderer for the method - // The handler for the event is provided as an argument to the function - interfaces[method] = (handler: any) => ipcRenderer.on(method, handler) -}) - -// Expose the 'interfaces' object in the main world under the name 'electronAPI' -// This allows the renderer process to access these methods directly -contextBridge.exposeInMainWorld('electronAPI', { - ...interfaces, - isQuickAsk: () => true, -}) diff --git a/electron/preload.ts b/electron/preload.ts deleted file mode 100644 index dbfcd1f1e..000000000 --- a/electron/preload.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Exposes a set of APIs to the renderer process via the contextBridge object. - * @module preload - */ - -import { APIEvents, APIRoutes, AppConfiguration } from '@janhq/core/node' -import { contextBridge, ipcRenderer } from 'electron' -import { readdirSync } from 'fs' - -const interfaces: { [key: string]: (...args: any[]) => any } = {} - -// Loop over each route in APIRoutes -APIRoutes.forEach((method) => { - // For each method, create a function on the interfaces object - // This function invokes the method on the ipcRenderer with any provided arguments - - interfaces[method] = (...args: any[]) => ipcRenderer.invoke(method, ...args) -}) - -// Loop over each method in APIEvents -APIEvents.forEach((method) => { - // For each method, create a function on the interfaces object - // This function sets up an event listener on the ipcRenderer for the method - // The handler for the event is provided as an argument to the function - interfaces[method] = (handler: any) => ipcRenderer.on(method, handler) -}) - -interfaces['changeDataFolder'] = async (path) => { - const appConfiguration: AppConfiguration = await ipcRenderer.invoke( - 'getAppConfigurations' - ) - const currentJanDataFolder = appConfiguration.data_folder - appConfiguration.data_folder = path - const reflect = require('@alumna/reflect') - const { err } = await reflect({ - src: currentJanDataFolder, - dest: path, - recursive: true, - delete: false, - overwrite: true, - errorOnExist: false, - }) - if (err) { - console.error(err) - throw err - } - await ipcRenderer.invoke('updateAppConfiguration', appConfiguration) -} - -interfaces['isDirectoryEmpty'] = async (path) => { - const dirChildren = await readdirSync(path) - return dirChildren.filter((x) => x !== '.DS_Store').length === 0 -} - -// Expose the 'interfaces' object in the main world under the name 'electronAPI' -// This allows the renderer process to access these methods directly -contextBridge.exposeInMainWorld('electronAPI', { - ...interfaces, - isQuickAsk: () => false, -}) diff --git a/electron/scripts/uninstaller.nsh b/electron/scripts/uninstaller.nsh deleted file mode 100644 index 684783258..000000000 --- a/electron/scripts/uninstaller.nsh +++ /dev/null @@ -1,46 +0,0 @@ -!include nsDialogs.nsh - -XPStyle on - -!macro customUnInstall - ${ifNot} ${isUpdated} - ; Define the process name of your Electron app - StrCpy $0 "Jan.exe" - - ; Check if the application is running - nsExec::ExecToStack 'tasklist /FI "IMAGENAME eq $0" /NH' - Pop $1 - - StrCmp $1 "" notRunning - - ; If the app is running, notify the user and attempt to close it - MessageBox MB_OK "Jan is being uninstalled, force close app." IDOK forceClose - - forceClose: - ; Attempt to kill the running application - nsExec::ExecToStack 'taskkill /F /IM $0' - Pop $1 - - ; Proceed with uninstallation - Goto continueUninstall - - notRunning: - ; If the app is not running, proceed with uninstallation - Goto continueUninstall - - continueUninstall: - ; Proceed with uninstallation - DeleteRegKey HKLM "Software\Jan" - RMDir /r "$INSTDIR" - Delete "$INSTDIR\*.*" - - ; Clean up shortcuts and app data - Delete "$DESKTOP\Jan.lnk" - Delete "$STARTMENU\Programs\Jan.lnk" - RMDir /r "$APPDATA\Jan" - RMDir /r "$LOCALAPPDATA\jan-updater" - - ; Close the uninstaller - Quit - ${endIf} -!macroend \ No newline at end of file diff --git a/electron/shared/.gitkeep b/electron/shared/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/electron/sign.js b/electron/sign.js deleted file mode 100644 index 9955e53e8..000000000 --- a/electron/sign.js +++ /dev/null @@ -1,69 +0,0 @@ -const { exec } = require('child_process') - -function execCommandWithRetry(command, retries = 3) { - return new Promise((resolve, reject) => { - const execute = (attempt) => { - exec(command, (error, stdout, stderr) => { - if (error) { - console.error(`Error: ${error}`) - if (attempt < retries) { - console.log(`Retrying... Attempt ${attempt + 1}`) - execute(attempt + 1) - } else { - return reject(error) - } - } else { - console.log(`stdout: ${stdout}`) - console.error(`stderr: ${stderr}`) - resolve() - } - }) - } - execute(0) - }) -} - -function sign({ - path, - name, - certUrl, - clientId, - tenantId, - clientSecret, - certName, - timestampServer, - version, -}) { - return new Promise((resolve, reject) => { - const command = `azuresigntool.exe sign -kvu "${certUrl}" -kvi "${clientId}" -kvt "${tenantId}" -kvs "${clientSecret}" -kvc "${certName}" -tr "${timestampServer}" -v "${path}"` - execCommandWithRetry(command) - .then(resolve) - .catch(reject) - }) -} - -exports.default = async function (options) { - const certUrl = process.env.AZURE_KEY_VAULT_URI - const clientId = process.env.AZURE_CLIENT_ID - const tenantId = process.env.AZURE_TENANT_ID - const clientSecret = process.env.AZURE_CLIENT_SECRET - const certName = process.env.AZURE_CERT_NAME - const timestampServer = 'http://timestamp.globalsign.com/tsa/r6advanced1' - - try { - await sign({ - path: options.path, - name: 'jan-win-x64', - certUrl, - clientId, - tenantId, - clientSecret, - certName, - timestampServer, - version: options.version, - }) - } catch (error) { - console.error('Failed to sign after 3 attempts:', error) - process.exit(1) - } -} diff --git a/electron/testRunner.js b/electron/testRunner.js deleted file mode 100644 index b0d108160..000000000 --- a/electron/testRunner.js +++ /dev/null @@ -1,10 +0,0 @@ -const jestRunner = require('jest-runner'); - -class EmptyTestFileRunner extends jestRunner.default { - async runTests(tests, watcher, onStart, onResult, onFailure, options) { - const nonEmptyTests = tests.filter(test => test.context.hasteFS.getSize(test.path) > 0); - return super.runTests(nonEmptyTests, watcher, onStart, onResult, onFailure, options); - } -} - -module.exports = EmptyTestFileRunner; \ No newline at end of file diff --git a/electron/tests/config/constants.ts b/electron/tests/config/constants.ts deleted file mode 100644 index 7039ad58c..000000000 --- a/electron/tests/config/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const Constants = { - VIDEO_DIR: './playwright-video', - TIMEOUT: '300000', -} diff --git a/electron/tests/config/fixtures.ts b/electron/tests/config/fixtures.ts deleted file mode 100644 index c36910161..000000000 --- a/electron/tests/config/fixtures.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - _electron as electron, - BrowserContext, - ElectronApplication, - expect, - Page, - test as base, -} from '@playwright/test' -import { - ElectronAppInfo, - findLatestBuild, - parseElectronApp, - stubDialog, -} from 'electron-playwright-helpers' -import { Constants } from './constants' -import { HubPage } from '../pages/hubPage' -import { CommonActions } from '../pages/commonActions' -import { rmSync } from 'fs' -import * as path from 'path' - -export let electronApp: ElectronApplication -export let page: Page -export let appInfo: ElectronAppInfo -export const TIMEOUT = parseInt(process.env.TEST_TIMEOUT || Constants.TIMEOUT) - -export async function setupElectron() { - console.log(`TEST TIMEOUT: ${TIMEOUT}`) - - process.env.CI = 'e2e' - - const latestBuild = findLatestBuild('dist') - expect(latestBuild).toBeTruthy() - - // parse the packaged Electron app and find paths and other info - appInfo = parseElectronApp(latestBuild) - expect(appInfo).toBeTruthy() - - electronApp = await electron.launch({ - args: [appInfo.main, '--no-sandbox'], // main file from package.json - executablePath: appInfo.executable, // path to the Electron executable - // recordVideo: { dir: Constants.VIDEO_DIR }, // Specify the directory for video recordings - }) - await stubDialog(electronApp, 'showMessageBox', { response: 1 }) - - page = await electronApp.firstWindow({ - timeout: TIMEOUT, - }) -} - -export async function teardownElectron() { - await page.close() - await electronApp.close() -} - -/** - * this fixture is needed to record and attach videos / screenshot on failed tests when - * tests are run in serial mode (i.e. browser is not closed between tests) - */ -export const test = base.extend< - { - commonActions: CommonActions - hubPage: HubPage - attachVideoPage: Page - attachScreenshotsToReport: void - }, - { createVideoContext: BrowserContext } ->({ - commonActions: async ({ request }, use, testInfo) => { - await use(new CommonActions(page, testInfo)) - }, - hubPage: async ({ commonActions }, use) => { - await use(new HubPage(page, commonActions)) - }, - createVideoContext: [ - async ({ playwright }, use) => { - const context = electronApp.context() - await use(context) - }, - { scope: 'worker' }, - ], - - attachVideoPage: [ - async ({ createVideoContext }, use, testInfo) => { - await use(page) - - if (testInfo.status !== testInfo.expectedStatus) { - const path = await createVideoContext.pages()[0].video()?.path() - await createVideoContext.close() - await testInfo.attach('video', { - path: path, - }) - } - }, - { scope: 'test', auto: true }, - ], - - attachScreenshotsToReport: [ - async ({ commonActions }, use, testInfo) => { - await use() - - // After the test, we can check whether the test passed or failed. - if (testInfo.status !== testInfo.expectedStatus) { - await commonActions.takeScreenshot('') - } - }, - { auto: true }, - ], -}) - -test.beforeAll(async () => { - rmSync(path.join(__dirname, '../../test-data'), { - recursive: true, - force: true, - }) - - test.setTimeout(TIMEOUT) - await setupElectron() - await page.waitForSelector('img[alt="Jan - Logo"]', { - state: 'visible', - timeout: TIMEOUT, - }) -}) - -test.afterAll(async () => { - // teardownElectron() -}) diff --git a/electron/tests/e2e/hub.e2e.spec.ts b/electron/tests/e2e/hub.e2e.spec.ts deleted file mode 100644 index 58d6a0854..000000000 --- a/electron/tests/e2e/hub.e2e.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test, appInfo, page, TIMEOUT } from '../config/fixtures' -import { expect } from '@playwright/test' - -test.beforeAll(async () => { - expect(appInfo).toMatchObject({ - asar: true, - executable: expect.anything(), - main: expect.anything(), - name: 'jan', - packageJson: expect.objectContaining({ name: 'jan' }), - platform: process.platform, - resourcesDir: expect.anything(), - }) -}) - -test('explores hub', async ({ hubPage }) => { - await hubPage.navigateByMenu() - await hubPage.verifyContainerVisible() - await hubPage.scrollToBottom() - const useModelBtn = page.getByTestId(/^setup-btn/).first() - - await expect(useModelBtn).toBeVisible({ - timeout: TIMEOUT, - }) -}) diff --git a/electron/tests/e2e/navigation.e2e.spec.ts b/electron/tests/e2e/navigation.e2e.spec.ts deleted file mode 100644 index 1b463d381..000000000 --- a/electron/tests/e2e/navigation.e2e.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { expect } from '@playwright/test' -import { page, test, TIMEOUT } from '../config/fixtures' - -test('renders left navigation panel', async () => { - const threadBtn = page.getByTestId('Thread').first() - await expect(threadBtn).toBeVisible({ timeout: TIMEOUT }) - // Chat section should be there - await page.getByTestId('Local API Server').first().click({ - timeout: TIMEOUT, - }) - const localServer = page.getByTestId('local-server-testid').first() - await expect(localServer).toBeVisible({ - timeout: TIMEOUT, - }) -}) diff --git a/electron/tests/e2e/settings.e2e.spec.ts b/electron/tests/e2e/settings.e2e.spec.ts deleted file mode 100644 index 06b4d1acc..000000000 --- a/electron/tests/e2e/settings.e2e.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { expect } from '@playwright/test' - -import { test, page, TIMEOUT } from '../config/fixtures' - -test('shows settings', async () => { - await page.getByTestId('Settings').first().click({ - timeout: TIMEOUT, - }) - const settingDescription = page.getByTestId('testid-setting-description') - await expect(settingDescription).toBeVisible({ timeout: TIMEOUT }) -}) diff --git a/electron/tests/e2e/thread.e2e.spec.ts b/electron/tests/e2e/thread.e2e.spec.ts deleted file mode 100644 index 41efc8437..000000000 --- a/electron/tests/e2e/thread.e2e.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { expect } from '@playwright/test' -import { page, test, TIMEOUT } from '../config/fixtures' - -test('show onboarding screen without any threads created or models downloaded', async () => { - await page.getByTestId('Thread').first().click({ - timeout: TIMEOUT, - }) - const denyButton = page.locator('[data-testid="btn-deny-product-analytics"]') - - if ((await denyButton.count()) > 0) { - await denyButton.click({ force: true }) - } - - const onboardScreen = page.getByTestId('onboard-screen') - await expect(onboardScreen).toBeVisible({ - timeout: TIMEOUT, - }) -}) diff --git a/electron/tests/pages/basePage.ts b/electron/tests/pages/basePage.ts deleted file mode 100644 index 11e3ba81a..000000000 --- a/electron/tests/pages/basePage.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Page, expect } from '@playwright/test' -import { CommonActions } from './commonActions' -import { TIMEOUT } from '../config/fixtures' - -export class BasePage { - menuId: string - - constructor( - protected readonly page: Page, - readonly action: CommonActions, - protected containerId: string - ) {} - - public getValue(key: string) { - return this.action.getValue(key) - } - - public setValue(key: string, value: string) { - this.action.setValue(key, value) - } - - async takeScreenshot(name: string = '') { - await this.action.takeScreenshot(name) - } - - async navigateByMenu() { - await this.clickFirstElement(this.menuId) - } - - async clickFirstElement(testId: string) { - await this.page.getByTestId(testId).first().click() - } - - async verifyContainerVisible() { - const container = this.page.getByTestId(this.containerId) - expect(container.isVisible()).toBeTruthy() - } - - async scrollToBottom() { - await this.page.evaluate(() => { - window.scrollTo(0, document.body.scrollHeight) - }) - } - - async waitUpdateLoader() { - await this.isElementVisible('img[alt="Jan - Logo"]') - } - - //wait and find a specific element with its selector and return Visible - async isElementVisible(selector: any) { - let isVisible = true - await this.page - .waitForSelector(selector, { state: 'visible', timeout: TIMEOUT }) - .catch(() => { - isVisible = false - }) - return isVisible - } -} diff --git a/electron/tests/pages/commonActions.ts b/electron/tests/pages/commonActions.ts deleted file mode 100644 index 08ea15f92..000000000 --- a/electron/tests/pages/commonActions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Page, TestInfo } from '@playwright/test' -import { page } from '../config/fixtures' - -export class CommonActions { - private testData = new Map() - - constructor( - public page: Page, - public testInfo: TestInfo - ) {} - - async takeScreenshot(name: string) { - const screenshot = await page.screenshot({ - fullPage: true, - }) - const attachmentName = `${this.testInfo.title}_${name || new Date().toISOString().slice(5, 19).replace(/[-:]/g, '').replace('T', '_')}` - await this.testInfo.attach(attachmentName.replace(/\s+/g, ''), { - body: screenshot, - contentType: 'image/png', - }) - } - - async hooks() { - console.log('hook from the scenario page') - } - - setValue(key: string, value: string) { - this.testData.set(key, value) - } - - getValue(key: string) { - return this.testData.get(key) - } -} diff --git a/electron/tests/pages/hubPage.ts b/electron/tests/pages/hubPage.ts deleted file mode 100644 index 0299ab15d..000000000 --- a/electron/tests/pages/hubPage.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Page } from '@playwright/test' -import { BasePage } from './basePage' -import { CommonActions } from './commonActions' - -export class HubPage extends BasePage { - readonly menuId: string = 'Hub' - static readonly containerId: string = 'hub-container-test-id' - - constructor( - public page: Page, - readonly action: CommonActions - ) { - super(page, action, HubPage.containerId) - } -} diff --git a/electron/tsconfig.json b/electron/tsconfig.json deleted file mode 100644 index 5116f0e88..000000000 --- a/electron/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "module": "commonjs", - "noImplicitAny": true, - "sourceMap": true, - "strict": true, - "outDir": "./build", - "rootDir": "./", - "noEmitOnError": true, - "esModuleInterop": true, - "baseUrl": ".", - "allowJs": true, - "skipLibCheck": true, - "paths": { "*": ["node_modules/*"] }, - "typeRoots": ["node_modules/@types"] - }, - "ts-node": { - "esm": true - }, - "include": ["./**/*.ts"], - "exclude": ["core", "build", "dist", "tests", "node_modules", "test-data"] -} diff --git a/electron/utils/clean.ts b/electron/utils/clean.ts deleted file mode 100644 index 12a68d39e..000000000 --- a/electron/utils/clean.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ModuleManager } from '@janhq/core/node' -import { windowManager } from './../managers/window' -import { dispose } from './disposable' -import { app } from 'electron' - -export function cleanUpAndQuit() { - if (!ModuleManager.instance.cleaningResource) { - ModuleManager.instance.cleaningResource = true - windowManager.cleanUp() - dispose(ModuleManager.instance.requiredModules) - ModuleManager.instance.clearImportedModules() - app.quit() - } -} diff --git a/electron/utils/dev.ts b/electron/utils/dev.ts deleted file mode 100644 index bd510096b..000000000 --- a/electron/utils/dev.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const setupReactDevTool = async () => { - // Which means you're running from source code - const { default: installExtension, REACT_DEVELOPER_TOOLS } = await import( - 'electron-devtools-installer' - ) // Don't use import on top level, since the installer package is dev-only - try { - const name = await installExtension(REACT_DEVELOPER_TOOLS) - console.debug(`Added Extension: ${name}`) - } catch (err) { - console.error('An error occurred while installing devtools:', err) - // Only log the error and don't throw it because it's not critical - } -} diff --git a/electron/utils/disposable.ts b/electron/utils/disposable.ts deleted file mode 100644 index 59018a775..000000000 --- a/electron/utils/disposable.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function dispose(requiredModules: Record) { - for (const key in requiredModules) { - const module = requiredModules[key] - if (typeof module['dispose'] === 'function') { - module['dispose']() - } - } -} diff --git a/electron/utils/extension.ts b/electron/utils/extension.ts deleted file mode 100644 index e055411a6..000000000 --- a/electron/utils/extension.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getJanExtensionsPath, init } from '@janhq/core/node' - -export const setupExtensions = async () => { - init({ - // Function to check from the main process that user wants to install a extension - confirmInstall: async (_extensions: string[]) => { - return true - }, - // Path to install extension to - extensionsPath: getJanExtensionsPath(), - }) -} diff --git a/electron/utils/logger.ts b/electron/utils/logger.ts deleted file mode 100644 index 48af0b93a..000000000 --- a/electron/utils/logger.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - createWriteStream, - existsSync, - mkdirSync, - readdir, - stat, - unlink, - writeFileSync, -} from 'fs' -import util from 'util' -import { - getAppConfigurations, - getJanDataFolderPath, - Logger, - LoggerManager, -} from '@janhq/core/node' -import path, { join } from 'path' - -/** - * File Logger - */ -export class FileLogger implements Logger { - name = 'file' - logCleaningInterval: number = 120000 - timeout: NodeJS.Timeout | undefined - appLogPath: string = './' - logEnabled: boolean = true - - constructor( - logEnabled: boolean = true, - logCleaningInterval: number = 120000 - ) { - this.logEnabled = logEnabled - if (logCleaningInterval) this.logCleaningInterval = logCleaningInterval - - const appConfigurations = getAppConfigurations() - const logFolderPath = join(appConfigurations.data_folder, 'logs') - if (!existsSync(logFolderPath)) { - mkdirSync(logFolderPath, { recursive: true }) - } - - this.appLogPath = join(logFolderPath, 'app.log') - } - - log(args: any) { - if (!this.logEnabled) return - let message = args[0] - const scope = args[1] - if (!message) return - const path = this.appLogPath - if (!scope && !message.startsWith('[')) { - message = `[APP]::${message}` - } else if (scope) { - message = `${scope}::${message}` - } - - message = `${new Date().toISOString()} ${message}` - - writeLog(message, path) - } - - cleanLogs( - maxFileSizeBytes?: number | undefined, - daysToKeep?: number | undefined - ): void { - // clear existing timeout - // in case we rerun it with different values - if (this.timeout) clearTimeout(this.timeout) - this.timeout = undefined - - if (!this.logEnabled) return - - console.log( - 'Validating app logs. Next attempt in ', - this.logCleaningInterval - ) - - const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB - const days = daysToKeep ?? 7 // 7 days - const logDirectory = path.join(getJanDataFolderPath(), 'logs') - // Perform log cleaning - const currentDate = new Date() - if (existsSync(logDirectory)) - readdir(logDirectory, (err, files) => { - if (err) { - console.error('Error reading log directory:', err) - return - } - - files.forEach((file) => { - const filePath = path.join(logDirectory, file) - stat(filePath, (err, stats) => { - if (err) { - console.error('Error getting file stats:', err) - return - } - - // Check size - if (stats.size > size) { - unlink(filePath, (err) => { - if (err) { - console.error('Error deleting log file:', err) - return - } - console.debug( - `Deleted log file due to exceeding size limit: ${filePath}` - ) - }) - } else { - // Check age - const creationDate = new Date(stats.ctime) - const daysDifference = Math.floor( - (currentDate.getTime() - creationDate.getTime()) / - (1000 * 3600 * 24) - ) - if (daysDifference > days) { - unlink(filePath, (err) => { - if (err) { - console.error('Error deleting log file:', err) - return - } - console.debug(`Deleted old log file: ${filePath}`) - }) - } - } - }) - }) - }) - - // Schedule the next execution with doubled delays - this.timeout = setTimeout( - () => this.cleanLogs(maxFileSizeBytes, daysToKeep), - this.logCleaningInterval - ) - } -} - -/** - * Write log function implementation - * @param message - * @param logPath - */ -const writeLog = (message: string, logPath: string) => { - if (!existsSync(logPath)) { - const logDirectory = path.join(getJanDataFolderPath(), 'logs') - if (!existsSync(logDirectory)) { - mkdirSync(logDirectory) - } - writeFileSync(logPath, message) - } else { - const logFile = createWriteStream(logPath, { - flags: 'a', - }) - logFile.write(util.format(message) + '\n') - logFile.close() - console.debug(message) - } -} - -/** - * Register logger for global application logging - */ -export const registerLogger = () => { - const logger = new FileLogger() - LoggerManager.instance().register(logger) - logger.cleanLogs() -} diff --git a/electron/utils/menu.ts b/electron/utils/menu.ts deleted file mode 100644 index bab70da79..000000000 --- a/electron/utils/menu.ts +++ /dev/null @@ -1,129 +0,0 @@ -// @ts-nocheck -import { app, Menu, shell, dialog } from 'electron' -import { autoUpdater } from 'electron-updater' -import { log } from '@janhq/core/node' -const isMac = process.platform === 'darwin' -import { windowManager } from '../managers/window' - -const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [ - { - label: app.name, - submenu: [ - { - label: `About ${app.name}`, - click: () => - dialog.showMessageBox({ - title: `Jan`, - message: `Jan Version v${app.getVersion()}\n\nCopyright © 2024 Jan`, - }), - }, - { - label: 'Check for Updates...', - click: () => - // Check for updates and notify user if there are any - autoUpdater - .checkForUpdatesAndNotify() - .then((updateCheckResult) => { - if ( - !updateCheckResult?.updateInfo || - updateCheckResult?.updateInfo.version === app.getVersion() - ) { - windowManager.mainWindow?.webContents.send( - AppEvent.onAppUpdateNotAvailable, - {} - ) - return - } - }) - .catch((error) => { - log('Error checking for updates:' + JSON.stringify(error)) - }), - }, - { type: 'separator' }, - { role: 'services' }, - { type: 'separator' }, - { role: 'hide' }, - { role: 'hideOthers' }, - { role: 'unhide' }, - { - label: `Settings`, - accelerator: 'CmdOrCtrl+,', - click: () => { - windowManager.showMainWindow() - windowManager.sendMainViewState('Settings') - }, - }, - { type: 'separator' }, - { role: 'quit' }, - ], - }, - { - label: 'Edit', - submenu: [ - { role: 'undo' }, - { role: 'redo' }, - { type: 'separator' }, - { role: 'cut' }, - { role: 'copy' }, - { role: 'paste' }, - ...(isMac - ? [ - { role: 'pasteAndMatchStyle' }, - { role: 'delete' }, - { role: 'selectAll' }, - { type: 'separator' }, - { - label: 'Speech', - submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }], - }, - ] - : [{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }]), - ], - }, - { - label: 'View', - submenu: [ - { role: 'reload' }, - { role: 'forceReload' }, - { role: 'toggleDevTools' }, - { type: 'separator' }, - { role: 'resetZoom' }, - { role: 'zoomIn' }, - { role: 'zoomOut' }, - { type: 'separator' }, - { role: 'togglefullscreen' }, - ], - }, - { - label: 'Window', - submenu: [ - { role: 'minimize' }, - { role: 'zoom' }, - ...(isMac - ? [ - { type: 'separator' }, - { role: 'front' }, - { type: 'separator' }, - { role: 'window' }, - ] - : [{ role: 'close' }]), - ], - }, - { - role: 'help', - submenu: [ - { - label: 'Learn More', - click: async () => { - await shell.openExternal('https://jan.ai/guides/') - }, - }, - ], - }, -] - -export const menu = Menu.buildFromTemplate(template) - -export const setupMenu = () => { - Menu.setApplicationMenu(menu) -} diff --git a/electron/utils/migration.ts b/electron/utils/migration.ts deleted file mode 100644 index 505de0f7b..000000000 --- a/electron/utils/migration.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { app } from 'electron' - -import { join } from 'path' -import { - rmdirSync, - existsSync, - mkdirSync, - readdirSync, - cpSync, - lstatSync, -} from 'fs' -import Store from 'electron-store' -import { - getJanDataFolderPath, - appResourcePath, - getJanExtensionsPath, -} from '@janhq/core/node' - -/** - * Migrates the extensions & themes. - * If the `migrated_version` key in the `Store` object does not match the current app version, - * the function deletes the `extensions` directory and sets the `migrated_version` key to the current app version. - * @returns A Promise that resolves when the migration is complete. - */ -export async function migrate() { - const store = new Store() - if (store.get('migrated_version') !== app.getVersion()) { - console.debug('start migration:', store.get('migrated_version')) - - if (existsSync(getJanExtensionsPath())) - rmdirSync(getJanExtensionsPath(), { recursive: true }) - - await migrateThemes() - - store.set('migrated_version', app.getVersion()) - console.debug('migrate extensions done') - } else if (!existsSync(join(getJanDataFolderPath(), 'themes'))) { - await migrateThemes() - } -} - -async function migrateThemes() { - if (!existsSync(join(getJanDataFolderPath(), 'themes'))) - mkdirSync(join(getJanDataFolderPath(), 'themes'), { recursive: true }) - - const themes = readdirSync(join(appResourcePath(), 'themes')) - for (const theme of themes) { - const themePath = join(appResourcePath(), 'themes', theme) - await checkAndMigrateTheme(theme, themePath) - } -} - -async function checkAndMigrateTheme( - sourceThemeName: string, - sourceThemePath: string -) { - const janDataThemesFolder = join(getJanDataFolderPath(), 'themes') - const existingTheme = readdirSync(janDataThemesFolder).find( - (theme) => theme === sourceThemeName - ) - if (existingTheme) { - const desTheme = join(janDataThemesFolder, existingTheme) - if (!lstatSync(desTheme).isDirectory()) { - return - } - console.debug('Updating theme', existingTheme) - rmdirSync(desTheme, { recursive: true }) - cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), { - recursive: true, - }) - } else { - console.debug('Adding new theme', sourceThemeName) - cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), { - recursive: true, - }) - } -} diff --git a/electron/utils/path.ts b/electron/utils/path.ts deleted file mode 100644 index 4438156bc..000000000 --- a/electron/utils/path.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { mkdir } from 'fs-extra' -import { existsSync } from 'fs' -import { getJanDataFolderPath } from '@janhq/core/node' - -export async function createUserSpace(): Promise { - const janDataFolderPath = getJanDataFolderPath() - if (!existsSync(janDataFolderPath)) { - try { - await mkdir(janDataFolderPath) - } catch (err) { - console.error( - `Unable to create Jan data folder at ${janDataFolderPath}: ${err}` - ) - } - } -} diff --git a/electron/utils/selectedText.ts b/electron/utils/selectedText.ts deleted file mode 100644 index 51b2eb762..000000000 --- a/electron/utils/selectedText.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { clipboard, globalShortcut } from 'electron' -import { keyboard, Key } from "@kirillvakalov/nut-tree__nut-js" - -/** - * Gets selected text by synthesizing the keyboard shortcut - * "CommandOrControl+c" then reading text from the clipboard - */ -export const getSelectedText = async () => { - const currentClipboardContent = clipboard.readText() // preserve clipboard content - clipboard.clear() - const hotkeys: Key[] = [ - process.platform === 'darwin' ? Key.LeftCmd : Key.LeftControl, - Key.C, - ] - await keyboard.pressKey(...hotkeys) - await keyboard.releaseKey(...hotkeys) - await new Promise((resolve) => setTimeout(resolve, 200)) // add a delay before checking clipboard - const selectedText = clipboard.readText() - clipboard.writeText(currentClipboardContent) - return selectedText -} - -/** - * Registers a global shortcut of `accelerator`. The `callback` is called - * with the selected text when the registered shortcut is pressed by the user - * - * Returns `true` if the shortcut was registered successfully - */ -export const registerShortcut = ( - accelerator: Electron.Accelerator, - callback: (selectedText: string) => void -) => { - return globalShortcut.register(accelerator, async () => { - callback(await getSelectedText()) - }) -} - -/** - * Unregisters a global shortcut of `accelerator` and - * is equivalent to electron.globalShortcut.unregister - */ -export const unregisterShortcut = (accelerator: Electron.Accelerator) => { - globalShortcut.unregister(accelerator) -} diff --git a/electron/utils/setup.ts b/electron/utils/setup.ts deleted file mode 100644 index 39b8a4133..000000000 --- a/electron/utils/setup.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { app, screen } from 'electron' -import Store from 'electron-store' - -const DEFAULT_WIDTH = 1000 -const DEFAULT_HEIGHT = 800 - -const storage = new Store() - -export const setupCore = async () => { - // Setup core api for main process - global.core = { - // Define appPath function for app to retrieve app path globally - appPath: () => app.getPath('userData'), - } -} - -export const getBounds = async () => { - const defaultBounds = { - x: undefined, - y: undefined, - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, - } - - const bounds = (await storage.get('windowBounds')) as - | Electron.Rectangle - | undefined - - // If no bounds are saved, use the defaults - if (!bounds) { - storage.set('windowBounds', defaultBounds) - return defaultBounds - } - - // Validate that the bounds are on a valid display - const displays = screen.getAllDisplays() - const isValid = displays.some((display) => { - const { x, y, width, height } = display.bounds - return ( - bounds.x >= x && - bounds.x < x + width && - bounds.y >= y && - bounds.y < y + height - ) - }) - - // If the position is valid, return the saved bounds, otherwise return default bounds - if (isValid) { - return bounds - } else { - const primaryDisplay = screen.getPrimaryDisplay() - const resetBounds = { - x: primaryDisplay.bounds.x, - y: primaryDisplay.bounds.y, - width: DEFAULT_WIDTH, - height: DEFAULT_HEIGHT, - } - storage.set('windowBounds', resetBounds) - return resetBounds - } -} - -export const saveBounds = (bounds: Electron.Rectangle | undefined) => { - storage.set('windowBounds', bounds) -} diff --git a/electron/utils/shortcut.ts b/electron/utils/shortcut.ts deleted file mode 100644 index aa4607d9a..000000000 --- a/electron/utils/shortcut.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getAppConfigurations } from '@janhq/core/node' -import { registerShortcut } from './selectedText' -import { windowManager } from '../managers/window' -// TODO: Retrieve from config later -const quickAskHotKey = 'CommandOrControl+J' - -export function registerGlobalShortcuts() { - if (!getAppConfigurations().quick_ask) return - const ret = registerShortcut(quickAskHotKey, (selectedText: string) => { - // Feature Toggle for Quick Ask - if (!windowManager.isQuickAskWindowVisible()) { - windowManager.showQuickAskWindow() - windowManager.sendQuickAskSelectedText(selectedText) - } else { - windowManager.hideQuickAskWindow() - } - }) - - if (!ret) { - console.error('Global shortcut registration failed') - } else { - console.log('Global shortcut registered successfully') - } -} diff --git a/electron/utils/system.ts b/electron/utils/system.ts deleted file mode 100644 index 5799de861..000000000 --- a/electron/utils/system.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { log } from '@janhq/core/node' -import { app } from 'electron' -import os from 'os' - -export const logSystemInfo = (): void => { - log(`[SPECS]::Version: ${app.getVersion()}`) - log(`[SPECS]::CPUs: ${JSON.stringify(os.cpus())}`) - log(`[SPECS]::Machine: ${os.machine()}`) - log(`[SPECS]::Endianness: ${os.endianness()}`) - log(`[SPECS]::Parallelism: ${os.availableParallelism()}`) - log(`[SPECS]::Free Mem: ${os.freemem()}`) - log(`[SPECS]::Total Mem: ${os.totalmem()}`) - log(`[SPECS]::OS Version: ${os.version()}`) - log(`[SPECS]::OS Platform: ${os.platform()}`) - log(`[SPECS]::OS Release: ${os.release()}`) -} diff --git a/extensions/assistant-extension/package.json b/extensions/assistant-extension/package.json index 08ccb3b3d..4761aa900 100644 --- a/extensions/assistant-extension/package.json +++ b/extensions/assistant-extension/package.json @@ -8,17 +8,10 @@ "author": "Jan ", "license": "AGPL-3.0", "scripts": { - "clean:modules": "rimraf node_modules/pdf-parse/test && cd node_modules/pdf-parse/lib/pdf.js && rimraf v1.9.426 v1.10.88 v2.0.550", - "build-universal-hnswlib": "[ \"$IS_TEST\" = \"true\" ] && echo \"Skip universal build\" || (cd node_modules/hnswlib-node && arch -x86_64 npx node-gyp rebuild --arch=x64 && mv build/Release/addon.node ./addon-amd64.node && node-gyp rebuild --arch=arm64 && mv build/Release/addon.node ./addon-arm64.node && lipo -create -output build/Release/addon.node ./addon-arm64.node ./addon-amd64.node && rm ./addon-arm64.node && rm ./addon-amd64.node)", - "build": "yarn clean:modules && rolldown -c rolldown.config.mjs", - "build:publish:linux": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install", - "build:publish:darwin": "rimraf *.tgz --glob || true && yarn build-universal-hnswlib && yarn build && ../../.github/scripts/auto-sign.sh && npm pack && cpx *.tgz ../../pre-install", - "build:publish:win32": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install", - "build:publish": "run-script-os", - "build:dev": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install" + "build": "rolldown -c rolldown.config.mjs", + "build:publish": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install" }, "devDependencies": { - "@types/pdf-parse": "^1.1.4", "cpx": "^1.5.0", "rimraf": "^3.0.2", "rolldown": "1.0.0-beta.1", @@ -27,11 +20,6 @@ }, "dependencies": { "@janhq/core": "../../core/package.tgz", - "@langchain/community": "0.0.13", - "hnswlib-node": "^1.4.2", - "langchain": "^0.0.214", - "node-gyp": "^11.0.0", - "pdf-parse": "^1.1.1", "ts-loader": "^9.5.0" }, "files": [ @@ -40,8 +28,7 @@ "README.md" ], "bundleDependencies": [ - "@janhq/core", - "hnswlib-node" + "@janhq/core" ], "installConfig": { "hoistingLimits": "workspaces" diff --git a/extensions/assistant-extension/rolldown.config.mjs b/extensions/assistant-extension/rolldown.config.mjs index e549ea7d9..436de93a8 100644 --- a/extensions/assistant-extension/rolldown.config.mjs +++ b/extensions/assistant-extension/rolldown.config.mjs @@ -13,22 +13,5 @@ export default defineConfig([ NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), VERSION: JSON.stringify(pkgJson.version), }, - }, - { - input: 'src/node/index.ts', - external: ['@janhq/core/node', 'path', 'hnswlib-node'], - output: { - format: 'cjs', - file: 'dist/node/index.js', - sourcemap: false, - inlineDynamicImports: true, - }, - resolve: { - extensions: ['.js', '.ts'], - }, - define: { - CORTEX_API_URL: JSON.stringify(`http://127.0.0.1:${process.env.CORTEX_API_PORT ?? "39291"}`), - }, - platform: 'node', - }, + } ]) diff --git a/extensions/assistant-extension/src/index.ts b/extensions/assistant-extension/src/index.ts index 621d8e216..6706e5ff3 100644 --- a/extensions/assistant-extension/src/index.ts +++ b/extensions/assistant-extension/src/index.ts @@ -1,37 +1,12 @@ -import { - fs, - Assistant, - events, - joinPath, - AssistantExtension, - AssistantEvent, - ToolManager, -} from '@janhq/core' -import { RetrievalTool } from './tools/retrieval' - +import { Assistant, AssistantExtension, fs, joinPath } from '@janhq/core' export default class JanAssistantExtension extends AssistantExtension { - private static readonly _homeDir = 'file://assistants' - async onLoad() { - // Register the retrieval tool - ToolManager.instance().register(new RetrievalTool()) - - // making the assistant directory - const assistantDirExist = await fs.existsSync( - JanAssistantExtension._homeDir - ) - if ( - localStorage.getItem(`${this.name}-version`) !== VERSION || - !assistantDirExist - ) { - if (!assistantDirExist) await fs.mkdir(JanAssistantExtension._homeDir) - - // Write assistant metadata - await this.createJanAssistant() - // Finished migration - localStorage.setItem(`${this.name}-version`, VERSION) - // Update the assistant list - events.emit(AssistantEvent.OnAssistantsUpdate, {}) + if (!(await fs.existsSync('file://assistants'))) { + await fs.mkdir('file://assistants') + } + const assistants = await this.getAssistants() + if (assistants.length === 0) { + await this.createAssistant(this.defaultAssistant) } } @@ -40,98 +15,67 @@ export default class JanAssistantExtension extends AssistantExtension { */ onUnload(): void {} - async createAssistant(assistant: Assistant): Promise { - const assistantDir = await joinPath([ - JanAssistantExtension._homeDir, - assistant.id, - ]) - if (!(await fs.existsSync(assistantDir))) await fs.mkdir(assistantDir) - - // store the assistant metadata json - const assistantMetadataPath = await joinPath([ - assistantDir, - 'assistant.json', - ]) - try { - await fs.writeFileSync( - assistantMetadataPath, - JSON.stringify(assistant, null, 2) - ) - } catch (err) { - console.error(err) + async getAssistants(): Promise { + if (!(await fs.existsSync('file://assistants'))) + return [this.defaultAssistant] + const assistants = await fs.readdirSync('file://assistants') + const assistantsData: Assistant[] = [] + for (const assistant of assistants) { + const assistantPath = await joinPath([ + 'file://assistants', + assistant, + 'assistant.json', + ]) + if (!(await fs.existsSync(assistantPath))) { + console.warn(`Assistant file not found: ${assistantPath}`) + continue + } + try { + const assistantData = JSON.parse(await fs.readFileSync(assistantPath)) + assistantsData.push(assistantData as Assistant) + } catch (error) { + console.error(`Failed to read assistant ${assistant}:`, error) + } } + return assistantsData } - async getAssistants(): Promise { - try { - // get all the assistant directories - // get all the assistant metadata json - const results: Assistant[] = [] - - const allFileName: string[] = await fs.readdirSync( - JanAssistantExtension._homeDir - ) - - for (const fileName of allFileName) { - const filePath = await joinPath([ - JanAssistantExtension._homeDir, - fileName, - ]) - - if (!(await fs.fileStat(filePath))?.isDirectory) continue - const jsonFiles: string[] = (await fs.readdirSync(filePath)).filter( - (file: string) => file === 'assistant.json' - ) - - if (jsonFiles.length !== 1) { - // has more than one assistant file -> ignore - continue - } - - const content = await fs.readFileSync( - await joinPath([filePath, jsonFiles[0]]), - 'utf-8' - ) - const assistant: Assistant = - typeof content === 'object' ? content : JSON.parse(content) - - results.push(assistant) - } - - return results - } catch (err) { - console.debug(err) - return [this.defaultAssistant] + async createAssistant(assistant: Assistant): Promise { + const assistantPath = await joinPath([ + 'file://assistants', + assistant.id, + 'assistant.json', + ]) + const assistantFolder = await joinPath(['file://assistants', assistant.id]) + if (!(await fs.existsSync(assistantFolder))) { + await fs.mkdir(assistantFolder) } + await fs.writeFileSync(assistantPath, JSON.stringify(assistant, null, 2)) } async deleteAssistant(assistant: Assistant): Promise { - if (assistant.id === 'jan') { - return Promise.reject('Cannot delete Jan Assistant') - } - - // remove the directory - const assistantDir = await joinPath([ - JanAssistantExtension._homeDir, + const assistantPath = await joinPath([ + 'file://assistants', assistant.id, + 'assistant.json', ]) - return fs.rm(assistantDir) - } - - private async createJanAssistant(): Promise { - await this.createAssistant(this.defaultAssistant) + if (await fs.existsSync(assistantPath)) { + await fs.rm(assistantPath) + } } private defaultAssistant: Assistant = { - avatar: '', + avatar: '👋', thread_location: undefined, id: 'jan', object: 'assistant', created_at: Date.now() / 1000, name: 'Jan', - description: 'A default assistant that can use all downloaded models', + description: + 'Jan is a helpful desktop assistant that can reason through complex tasks and use tools to complete them on the user’s behalf.', model: '*', - instructions: '', + instructions: + 'Jan is a helpful desktop assistant that can reason through complex tasks and use tools to complete them on the user’s behalf. Respond naturally and concisely, take actions when needed, and guide the user toward their goals.', tools: [ { type: 'retrieval', diff --git a/extensions/assistant-extension/src/node/index.ts b/extensions/assistant-extension/src/node/index.ts deleted file mode 100644 index 731890b34..000000000 --- a/extensions/assistant-extension/src/node/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getJanDataFolderPath } from '@janhq/core/node' -import { retrieval } from './retrieval' -import path from 'path' - -export function toolRetrievalUpdateTextSplitter( - chunkSize: number, - chunkOverlap: number -) { - retrieval.updateTextSplitter(chunkSize, chunkOverlap) -} -export async function toolRetrievalIngestNewDocument( - thread: string, - file: string, - model: string, - engine: string, - useTimeWeighted: boolean -) { - const threadPath = path.join(getJanDataFolderPath(), 'threads', thread) - const filePath = path.join(getJanDataFolderPath(), 'files', file) - retrieval.updateEmbeddingEngine(model, engine) - return retrieval - .ingestAgentKnowledge(filePath, `${threadPath}/memory`, useTimeWeighted) - .catch((err) => { - console.error(err) - }) -} - -export async function toolRetrievalLoadThreadMemory(threadId: string) { - return retrieval - .loadRetrievalAgent( - path.join(getJanDataFolderPath(), 'threads', threadId, 'memory') - ) - .catch((err) => { - console.error(err) - }) -} - -export async function toolRetrievalQueryResult( - query: string, - useTimeWeighted: boolean = false -) { - return retrieval.generateResult(query, useTimeWeighted).catch((err) => { - console.error(err) - }) -} diff --git a/extensions/assistant-extension/src/node/retrieval.ts b/extensions/assistant-extension/src/node/retrieval.ts deleted file mode 100644 index 05fa67d54..000000000 --- a/extensions/assistant-extension/src/node/retrieval.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter' -import { formatDocumentsAsString } from 'langchain/util/document' -import { PDFLoader } from 'langchain/document_loaders/fs/pdf' - -import { TimeWeightedVectorStoreRetriever } from 'langchain/retrievers/time_weighted' -import { MemoryVectorStore } from 'langchain/vectorstores/memory' - -import { HNSWLib } from 'langchain/vectorstores/hnswlib' - -import { OpenAIEmbeddings } from 'langchain/embeddings/openai' - -export class Retrieval { - public chunkSize: number = 100 - public chunkOverlap?: number = 0 - private retriever: any - - private embeddingModel?: OpenAIEmbeddings = undefined - private textSplitter?: RecursiveCharacterTextSplitter - - // to support time-weighted retrieval - private timeWeightedVectorStore: MemoryVectorStore - private timeWeightedretriever: any | TimeWeightedVectorStoreRetriever - - constructor(chunkSize: number = 4000, chunkOverlap: number = 200) { - this.updateTextSplitter(chunkSize, chunkOverlap) - this.initialize() - } - - private async initialize() { - const apiKey = await window.core?.api.appToken() ?? 'cortex.cpp' - - // declare time-weighted retriever and storage - this.timeWeightedVectorStore = new MemoryVectorStore( - new OpenAIEmbeddings( - { openAIApiKey: apiKey }, - { basePath: `${CORTEX_API_URL}/v1` } - ) - ) - this.timeWeightedretriever = new TimeWeightedVectorStoreRetriever({ - vectorStore: this.timeWeightedVectorStore, - memoryStream: [], - searchKwargs: 2, - }) - } - - public updateTextSplitter(chunkSize: number, chunkOverlap: number): void { - this.chunkSize = chunkSize - this.chunkOverlap = chunkOverlap - this.textSplitter = new RecursiveCharacterTextSplitter({ - chunkSize: chunkSize, - chunkOverlap: chunkOverlap, - }) - } - - public async updateEmbeddingEngine(model: string, engine: string) { - const apiKey = await window.core?.api.appToken() ?? 'cortex.cpp' - this.embeddingModel = new OpenAIEmbeddings( - { openAIApiKey: apiKey, model }, - // TODO: Raw settings - { basePath: `${CORTEX_API_URL}/v1` } - ) - - // update time-weighted embedding model - this.timeWeightedVectorStore.embeddings = this.embeddingModel - } - - public ingestAgentKnowledge = async ( - filePath: string, - memoryPath: string, - useTimeWeighted: boolean - ): Promise => { - const loader = new PDFLoader(filePath, { - splitPages: true, - }) - if (!this.embeddingModel) return Promise.reject() - const doc = await loader.load() - const docs = await this.textSplitter!.splitDocuments(doc) - const vectorStore = await HNSWLib.fromDocuments(docs, this.embeddingModel) - - // add documents with metadata by using the time-weighted retriever in order to support time-weighted retrieval - if (useTimeWeighted && this.timeWeightedretriever) { - await ( - this.timeWeightedretriever as TimeWeightedVectorStoreRetriever - ).addDocuments(docs) - } - return vectorStore.save(memoryPath) - } - - public loadRetrievalAgent = async (memoryPath: string): Promise => { - if (!this.embeddingModel) return Promise.reject() - const vectorStore = await HNSWLib.load(memoryPath, this.embeddingModel) - this.retriever = vectorStore.asRetriever(2) - return Promise.resolve() - } - - public generateResult = async ( - query: string, - useTimeWeighted: boolean - ): Promise => { - if (useTimeWeighted) { - if (!this.timeWeightedretriever) { - return Promise.resolve(' ') - } - // use invoke because getRelevantDocuments is deprecated - const relevantDocs = await this.timeWeightedretriever.invoke(query) - const serializedDoc = formatDocumentsAsString(relevantDocs) - return Promise.resolve(serializedDoc) - } - - if (!this.retriever) { - return Promise.resolve(' ') - } - - // should use invoke(query) because getRelevantDocuments is deprecated - const relevantDocs = await this.retriever.getRelevantDocuments(query) - const serializedDoc = formatDocumentsAsString(relevantDocs) - return Promise.resolve(serializedDoc) - } -} - -export const retrieval = new Retrieval() diff --git a/extensions/assistant-extension/src/tools/retrieval.ts b/extensions/assistant-extension/src/tools/retrieval.ts deleted file mode 100644 index b1a0c3cba..000000000 --- a/extensions/assistant-extension/src/tools/retrieval.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - AssistantTool, - executeOnMain, - fs, - InferenceTool, - joinPath, - MessageRequest, -} from '@janhq/core' - -export class RetrievalTool extends InferenceTool { - private _threadDir = 'file://threads' - private retrievalThreadId: string | undefined = undefined - - name: string = 'retrieval' - - async process( - data: MessageRequest, - tool?: AssistantTool - ): Promise { - if (!data.model || !data.messages) { - return Promise.resolve(data) - } - - const latestMessage = data.messages[data.messages.length - 1] - - // 1. Ingest the document if needed - if ( - latestMessage && - latestMessage.content && - typeof latestMessage.content !== 'string' && - latestMessage.content.length > 1 - ) { - const docFile = latestMessage.content[1]?.doc_url?.url - if (docFile) { - await executeOnMain( - NODE, - 'toolRetrievalIngestNewDocument', - data.thread?.id, - docFile, - data.model?.id, - data.model?.engine, - tool?.useTimeWeightedRetriever ?? false - ) - } else { - return Promise.resolve(data) - } - } else if ( - // Check whether we need to ingest document or not - // Otherwise wrong context will be sent - !(await fs.existsSync( - await joinPath([this._threadDir, data.threadId, 'memory']) - )) - ) { - // No document ingested, reroute the result to inference engine - - return Promise.resolve(data) - } - // 2. Load agent on thread changed - if (this.retrievalThreadId !== data.threadId) { - await executeOnMain(NODE, 'toolRetrievalLoadThreadMemory', data.threadId) - - this.retrievalThreadId = data.threadId - - // Update the text splitter - await executeOnMain( - NODE, - 'toolRetrievalUpdateTextSplitter', - tool?.settings?.chunk_size ?? 4000, - tool?.settings?.chunk_overlap ?? 200 - ) - } - - // 3. Using the retrieval template with the result and query - if (latestMessage.content) { - const prompt = - typeof latestMessage.content === 'string' - ? latestMessage.content - : latestMessage.content[0].text - // Retrieve the result - const retrievalResult = await executeOnMain( - NODE, - 'toolRetrievalQueryResult', - prompt, - tool?.useTimeWeightedRetriever ?? false - ) - console.debug('toolRetrievalQueryResult', retrievalResult) - - // Update message content - if (retrievalResult) - data.messages[data.messages.length - 1].content = - tool?.settings?.retrieval_template - ?.replace('{CONTEXT}', retrievalResult) - .replace('{QUESTION}', prompt) - } - - // 4. Reroute the result to inference engine - return Promise.resolve(this.normalize(data)) - } - - // Filter out all the messages that are not text - // TODO: Remove it until engines can handle multiple content types - normalize(request: MessageRequest): MessageRequest { - request.messages = request.messages?.map((message) => { - if ( - message.content && - typeof message.content !== 'string' && - (message.content.length ?? 0) > 0 - ) { - return { - ...message, - content: [message.content[0]], - } - } - return message - }) - return request - } -} diff --git a/extensions/conversational-extension/package.json b/extensions/conversational-extension/package.json index a5224b99b..abb76e4d0 100644 --- a/extensions/conversational-extension/package.json +++ b/extensions/conversational-extension/package.json @@ -2,7 +2,7 @@ "name": "@janhq/conversational-extension", "productName": "Conversational", "version": "1.0.0", - "description": "Enables conversations and state persistence via your filesystem.", + "description": "Enables conversations and state persistence via your file system.", "main": "dist/index.js", "author": "Jan ", "license": "MIT", @@ -23,9 +23,7 @@ "typescript": "^5.7.2" }, "dependencies": { - "@janhq/core": "../../core/package.tgz", - "ky": "^1.7.2", - "p-queue": "^8.0.1" + "@janhq/core": "../../core/package.tgz" }, "engines": { "node": ">=18.0.0" diff --git a/extensions/conversational-extension/src/index.ts b/extensions/conversational-extension/src/index.ts index 791385fc9..720291d88 100644 --- a/extensions/conversational-extension/src/index.ts +++ b/extensions/conversational-extension/src/index.ts @@ -4,45 +4,17 @@ import { ThreadAssistantInfo, ThreadMessage, } from '@janhq/core' -import ky, { KyInstance } from 'ky' -import PQueue from 'p-queue' - -type ThreadList = { - data: Thread[] -} - -type MessageList = { - data: ThreadMessage[] -} /** * JSONConversationalExtension is a ConversationalExtension implementation that provides * functionality for managing threads. */ export default class CortexConversationalExtension extends ConversationalExtension { - queue = new PQueue({ concurrency: 1 }) - - api?: KyInstance - /** - * Get the API instance - * @returns - */ - async apiInstance(): Promise { - if(this.api) return this.api - const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' - this.api = ky.extend({ - prefixUrl: API_URL, - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }) - return this.api - } /** * Called when the extension is loaded. */ async onLoad() { - this.queue.add(() => this.healthz()) + // this.queue.add(() => this.healthz()) } /** @@ -54,14 +26,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * Returns a Promise that resolves to an array of Conversation objects. */ async listThreads(): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get('v1/threads?limit=-1') - .json() - .then((e) => e.data) - ) - ) as Promise + return window.core.api.listThreads() } /** @@ -69,11 +34,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @param thread The Thread object to save. */ async createThread(thread: Thread): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api.post('v1/threads', { json: thread }).json() - ) - ) as Promise + return window.core.api.createThread({ thread }) } /** @@ -81,13 +42,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @param thread The Thread object to save. */ async modifyThread(thread: Thread): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api.patch(`v1/threads/${thread.id}`, { json: thread }) - ) - ) - .then() + return window.core.api.modifyThread({ thread }) } /** @@ -95,11 +50,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @param threadId The ID of the thread to delete. */ async deleteThread(threadId: string): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => api.delete(`v1/threads/${threadId}`)) - ) - .then() + return window.core.api.deleteThread({ threadId }) } /** @@ -108,15 +59,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @returns A Promise that resolves when the message has been added. */ async createMessage(message: ThreadMessage): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post(`v1/threads/${message.thread_id}/messages`, { - json: message, - }) - .json() - ) - ) as Promise + return window.core.api.createMessage({ message }) } /** @@ -125,15 +68,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @returns */ async modifyMessage(message: ThreadMessage): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .patch(`v1/threads/${message.thread_id}/messages/${message.id}`, { - json: message, - }) - .json() - ) - ) as Promise + return window.core.api.modifyMessage({ message }) } /** @@ -143,13 +78,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @returns A Promise that resolves when the message has been successfully deleted. */ async deleteMessage(threadId: string, messageId: string): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api.delete(`v1/threads/${threadId}/messages/${messageId}`) - ) - ) - .then() + return window.core.api.deleteMessage({ threadId, messageId }) } /** @@ -158,14 +87,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * @returns A Promise that resolves to an array of ThreadMessage objects. */ async listMessages(threadId: string): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/threads/${threadId}/messages?order=asc&limit=-1`) - .json() - .then((e) => e.data) - ) - ) as Promise + return window.core.api.listMessages({ threadId }) } /** @@ -175,13 +97,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi * the details of the assistant associated with the specified thread. */ async getThreadAssistant(threadId: string): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/assistants/${threadId}?limit=-1`) - .json() - ) - ) as Promise + return window.core.api.getThreadAssistant({ threadId }) } /** * Creates a new assistant for the specified thread. @@ -193,13 +109,7 @@ export default class CortexConversationalExtension extends ConversationalExtensi threadId: string, assistant: ThreadAssistantInfo ): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post(`v1/assistants/${threadId}`, { json: assistant }) - .json() - ) - ) as Promise + return window.core.api.createThreadAssistant(threadId, assistant) } /** @@ -212,26 +122,6 @@ export default class CortexConversationalExtension extends ConversationalExtensi threadId: string, assistant: ThreadAssistantInfo ): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .patch(`v1/assistants/${threadId}`, { json: assistant }) - .json() - ) - ) as Promise - } - - /** - * Do health check on cortex.cpp - * @returns - */ - async healthz(): Promise { - return this.apiInstance() - .then((api) => - api.get('healthz', { - retry: { limit: 20, delay: () => 500, methods: ['get'] }, - }) - ) - .then(() => {}) + return window.core.api.modifyThreadAssistant({ threadId, assistant }) } } diff --git a/extensions/download-extension/package.json b/extensions/download-extension/package.json new file mode 100644 index 000000000..750934594 --- /dev/null +++ b/extensions/download-extension/package.json @@ -0,0 +1,36 @@ +{ + "name": "@janhq/download-extension", + "productName": "Download Manager", + "version": "1.0.0", + "description": "Handle downloads", + "main": "dist/index.js", + "author": "Jan ", + "license": "AGPL-3.0", + "scripts": { + "test": "vitest run", + "build": "rolldown -c rolldown.config.mjs", + "build:publish": "rimraf *.tgz --glob || true && yarn build && npm pack && cpx *.tgz ../../pre-install" + }, + "devDependencies": { + "cpx": "^1.5.0", + "rimraf": "^3.0.2", + "rolldown": "1.0.0-beta.1", + "run-script-os": "^1.1.6", + "typescript": "5.3.3", + "vitest": "^3.0.6" + }, + "files": [ + "dist/*", + "package.json", + "README.md" + ], + "dependencies": { + "@janhq/core": "../../core/package.tgz", + "@tauri-apps/api": "^2.5.0" + }, + "bundleDependencies": [], + "installConfig": { + "hoistingLimits": "workspaces" + }, + "packageManager": "yarn@4.5.3" +} diff --git a/extensions/download-extension/rolldown.config.mjs b/extensions/download-extension/rolldown.config.mjs new file mode 100644 index 000000000..e9b190546 --- /dev/null +++ b/extensions/download-extension/rolldown.config.mjs @@ -0,0 +1,14 @@ +import { defineConfig } from 'rolldown' +import settingJson from './settings.json' with { type: 'json' } + +export default defineConfig({ + input: 'src/index.ts', + output: { + format: 'esm', + file: 'dist/index.js', + }, + platform: 'browser', + define: { + SETTINGS: JSON.stringify(settingJson), + }, +}) diff --git a/extensions/download-extension/settings.json b/extensions/download-extension/settings.json new file mode 100644 index 000000000..f2f50762f --- /dev/null +++ b/extensions/download-extension/settings.json @@ -0,0 +1,14 @@ +[ + { + "key": "hf-token", + "title": "Hugging Face Access Token", + "description": "Access tokens programmatically authenticate your identity to the Hugging Face Hub, allowing applications to perform specific actions specified by the scope of permissions granted.", + "controllerType": "input", + "controllerProps": { + "value": "", + "placeholder": "hf_**********************************", + "type": "password", + "inputActions": ["unobscure", "copy"] + } + } +] diff --git a/extensions/download-extension/src/@types/global.d.ts b/extensions/download-extension/src/@types/global.d.ts new file mode 100644 index 000000000..4ff21449c --- /dev/null +++ b/extensions/download-extension/src/@types/global.d.ts @@ -0,0 +1 @@ +declare const SETTINGS: SettingComponentProps[] diff --git a/extensions/download-extension/src/index.ts b/extensions/download-extension/src/index.ts new file mode 100644 index 000000000..11315ba85 --- /dev/null +++ b/extensions/download-extension/src/index.ts @@ -0,0 +1,82 @@ +import { invoke } from '@tauri-apps/api/core'; +import { listen } from '@tauri-apps/api/event'; +import { BaseExtension, events } from '@janhq/core'; + +export enum Settings { + hfToken = 'hf-token', +} + +interface DownloadItem { + url: string + save_path: string +} + +type DownloadEvent = { + transferred: number + total: number +} + +export default class DownloadManager extends BaseExtension { + hfToken?: string + + async onLoad() { + this.registerSettings(SETTINGS) + this.hfToken = await this.getSetting(Settings.hfToken, undefined) + } + + async onUnload() { } + + async downloadFile( + url: string, + savePath: string, + taskId: string, + onProgress?: (transferred: number, total: number) => void + ) { + return await this.downloadFiles( + [{ url, save_path: savePath }], + taskId, + onProgress + ) + } + + async downloadFiles( + items: DownloadItem[], + taskId: string, + onProgress?: (transferred: number, total: number) => void + ) { + // relay tauri events to onProgress callback + const unlisten = await listen(`download-${taskId}`, (event) => { + if (onProgress) { + let payload = event.payload + onProgress(payload.transferred, payload.total) + } + }) + + try { + await invoke( + "download_files", + { items, taskId, headers: this._getHeaders() }, + ) + } catch (error) { + console.error("Error downloading task", taskId, error) + throw error + } finally { + unlisten() + } + } + + async cancelDownload(taskId: string) { + try { + await invoke("cancel_download_task", { taskId }) + } catch (error) { + console.error("Error cancelling download:", error) + throw error + } + } + + _getHeaders() { + return { + ...(this.hfToken && { Authorization: `Bearer ${this.hfToken}` }) + } + } +} diff --git a/extensions/download-extension/tsconfig.json b/extensions/download-extension/tsconfig.json new file mode 100644 index 000000000..1d3c112d4 --- /dev/null +++ b/extensions/download-extension/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "esnext", + "moduleResolution": "node", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "skipLibCheck": true, + "rootDir": "./src" + }, + "include": ["./src"], + "exclude": ["**/*.test.ts", "vite.config.ts"] +} diff --git a/extensions/download-extension/vite.config.ts b/extensions/download-extension/vite.config.ts new file mode 100644 index 000000000..a8ad5615f --- /dev/null +++ b/extensions/download-extension/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vite" +export default defineConfig(({ mode }) => ({ + define: process.env.VITEST ? {} : { global: 'window' }, + test: { + environment: 'jsdom', + }, +})) + diff --git a/extensions/engine-management-extension/resources/google_gemini.json b/extensions/engine-management-extension/resources/google_gemini.json index e0fa809a5..f860a1990 100644 --- a/extensions/engine-management-extension/resources/google_gemini.json +++ b/extensions/engine-management-extension/resources/google_gemini.json @@ -5,7 +5,7 @@ "url": "https://aistudio.google.com/apikey", "api_key": "", "metadata": { - "get_models_url": "https://generativelanguage.googleapis.com/v1beta/models", + "get_models_url": "https://generativelanguage.googleapis.com/openai/v1beta/models", "header_template": "Authorization: Bearer {{api_key}}", "transform_req": { "chat_completions": { diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 7d6a6c1af..98a5445cf 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -15,7 +15,7 @@ export default defineConfig([ `http://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` ), PLATFORM: JSON.stringify(process.platform), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.56'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5509'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify( @@ -38,7 +38,7 @@ export default defineConfig([ file: 'dist/node/index.cjs.js', }, define: { - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.56'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5509'), }, }, ]) diff --git a/extensions/engine-management-extension/src/index.ts b/extensions/engine-management-extension/src/index.ts index 7d0c9f9c4..34195c8cc 100644 --- a/extensions/engine-management-extension/src/index.ts +++ b/extensions/engine-management-extension/src/index.ts @@ -1,6 +1,5 @@ import { EngineManagementExtension, - InferenceEngine, DefaultEngineVariant, Engines, EngineConfig, @@ -16,7 +15,6 @@ import { EngineEvent, } from '@janhq/core' import ky, { HTTPError, KyInstance } from 'ky' -import PQueue from 'p-queue' import { EngineError } from './error' import { getJanDataFolderPath } from '@janhq/core' import { engineVariant } from './utils' @@ -29,21 +27,22 @@ interface ModelList { * functionality for managing engines. */ export default class JanEngineManagementExtension extends EngineManagementExtension { - queue = new PQueue({ concurrency: 1 }) - api?: KyInstance /** * Get the API instance * @returns */ async apiInstance(): Promise { - if(this.api) return this.api - const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' + if (this.api) return this.api + const apiKey = await window.core?.api.appToken() this.api = ky.extend({ prefixUrl: API_URL, - headers: { - Authorization: `Bearer ${apiKey}`, - }, + headers: apiKey + ? { + Authorization: `Bearer ${apiKey}`, + } + : {}, + retry: 10, }) return this.api } @@ -51,16 +50,9 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * Called when the extension is loaded. */ async onLoad() { - // Symlink Engines Directory - await executeOnMain(NODE, 'symlinkEngines') - // Run Healthcheck - this.queue.add(() => this.healthz()) // Update default local engine this.updateDefaultEngine() - // Populate default remote engines - this.populateDefaultRemoteEngines() - // Migrate this.migrate() } @@ -74,13 +66,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @returns A Promise that resolves to an object of list engines. */ async getEngines(): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get('v1/engines') - .json() - .then((e) => e) - ) + return this.apiInstance().then((api) => + api + .get('v1/engines') + .json() + .then((e) => e) ) as Promise } @@ -103,14 +93,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @param name - Inference engine name. * @returns A Promise that resolves to an array of installed engine. */ - async getInstalledEngines(name: InferenceEngine): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/engines/${name}`) - .json() - .then((e) => e) - ) + async getInstalledEngines(name: string): Promise { + return this.apiInstance().then((api) => + api + .get(`v1/engines/${name}`) + .json() + .then((e) => e) ) as Promise } @@ -121,19 +109,17 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @returns A Promise that resolves to an array of latest released engine by version. */ async getReleasedEnginesByVersion( - name: InferenceEngine, + name: string, version: string, platform?: string ) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/engines/${name}/releases/${version}`) - .json() - .then((e) => - platform ? e.filter((r) => r.name.includes(platform)) : e - ) - ) + return this.apiInstance().then((api) => + api + .get(`v1/engines/${name}/releases/${version}`) + .json() + .then((e) => + platform ? e.filter((r) => r.name.includes(platform)) : e + ) ) as Promise } @@ -142,16 +128,14 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @param platform - Optional to sort by operating system. macOS, linux, windows. * @returns A Promise that resolves to an array of latest released engine by version. */ - async getLatestReleasedEngine(name: InferenceEngine, platform?: string) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/engines/${name}/releases/latest`) - .json() - .then((e) => - platform ? e.filter((r) => r.name.includes(platform)) : e - ) - ) + async getLatestReleasedEngine(name: string, platform?: string) { + return this.apiInstance().then((api) => + api + .get(`v1/engines/${name}/releases/latest`) + .json() + .then((e) => + platform ? e.filter((r) => r.name.includes(platform)) : e + ) ) as Promise } @@ -160,12 +144,10 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @returns A Promise that resolves to intall of engine. */ async installEngine(name: string, engineConfig: EngineConfig) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post(`v1/engines/${name}/install`, { json: engineConfig }) - .then((e) => e) - ) + return this.apiInstance().then((api) => + api + .post(`v1/engines/${name}/install`, { json: engineConfig }) + .then((e) => e) ) as Promise<{ messages: string }> } @@ -195,18 +177,16 @@ export default class JanEngineManagementExtension extends EngineManagementExtens if (engineConfig.metadata && !engineConfig.metadata?.header_template) engineConfig.metadata.header_template = DEFAULT_REQUEST_HEADERS_TRANSFORM - return this.queue.add(() => - this.apiInstance().then((api) => - api.post('v1/engines', { json: engineConfig }).then((e) => { - if (persistModels && engineConfig.metadata?.get_models_url) { - // Pull /models from remote models endpoint - return this.populateRemoteModels(engineConfig) - .then(() => e) - .catch(() => e) - } - return e - }) - ) + return this.apiInstance().then((api) => + api.post('v1/engines', { json: engineConfig }).then((e) => { + if (persistModels && engineConfig.metadata?.get_models_url) { + // Pull /models from remote models endpoint + return this.populateRemoteModels(engineConfig) + .then(() => e) + .catch(() => e) + } + return e + }) ) as Promise<{ messages: string }> } @@ -214,13 +194,11 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @param name - Inference engine name. * @returns A Promise that resolves to unintall of engine. */ - async uninstallEngine(name: InferenceEngine, engineConfig: EngineConfig) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .delete(`v1/engines/${name}/install`, { json: engineConfig }) - .then((e) => e) - ) + async uninstallEngine(name: string, engineConfig: EngineConfig) { + return this.apiInstance().then((api) => + api + .delete(`v1/engines/${name}/install`, { json: engineConfig }) + .then((e) => e) ) as Promise<{ messages: string }> } @@ -229,25 +207,22 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @param model - Remote model object. */ async addRemoteModel(model: Model) { - return this.queue.add(() => - this.apiInstance() - .then((api) => - api - .post('v1/models/add', { - json: { - inference_params: { - max_tokens: 4096, - temperature: 0.7, - top_p: 0.95, - stream: true, - frequency_penalty: 0, - presence_penalty: 0, - }, - ...model, - }, - }) - .then((e) => e) - ) + return this.apiInstance().then((api) => + api + .post('v1/models/add', { + json: { + inference_params: { + max_tokens: 4096, + temperature: 0.7, + top_p: 0.95, + stream: true, + frequency_penalty: 0, + presence_penalty: 0, + }, + ...model, + }, + }) + .then((e) => e) .then(() => {}) ) } @@ -256,14 +231,12 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @param name - Inference engine name. * @returns A Promise that resolves to an object of default engine. */ - async getDefaultEngineVariant(name: InferenceEngine) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/engines/${name}/default`) - .json<{ messages: string }>() - .then((e) => e) - ) + async getDefaultEngineVariant(name: string) { + return this.apiInstance().then((api) => + api + .get(`v1/engines/${name}/default`) + .json<{ messages: string }>() + .then((e) => e) ) as Promise } @@ -272,60 +245,41 @@ export default class JanEngineManagementExtension extends EngineManagementExtens * @body version - string * @returns A Promise that resolves to set default engine. */ - async setDefaultEngineVariant( - name: InferenceEngine, - engineConfig: EngineConfig - ) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post(`v1/engines/${name}/default`, { json: engineConfig }) - .then((e) => e) - ) + async setDefaultEngineVariant(name: string, engineConfig: EngineConfig) { + return this.apiInstance().then((api) => + api + .post(`v1/engines/${name}/default`, { json: engineConfig }) + .then((e) => e) ) as Promise<{ messages: string }> } /** * @returns A Promise that resolves to update engine. */ - async updateEngine(name: InferenceEngine, engineConfig?: EngineConfig) { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post(`v1/engines/${name}/update`, { json: engineConfig }) - .then((e) => e) - ) + async updateEngine(name: string, engineConfig?: EngineConfig) { + return this.apiInstance().then((api) => + api + .post(`v1/engines/${name}/update`, { json: engineConfig }) + .then((e) => e) ) as Promise<{ messages: string }> } - /** - * Do health check on cortex.cpp - * @returns - */ - async healthz(): Promise { - return this.apiInstance() - .then((api) => - api.get('healthz', { - retry: { limit: 20, delay: () => 500, methods: ['get'] }, - }) - ) - .then(() => { - this.queue.concurrency = Infinity - }) - } - /** * Update default local engine * This is to use built-in engine variant in case there is no default engine set */ async updateDefaultEngine() { + const systemInfo = await systemInformation() try { - const variant = await this.getDefaultEngineVariant( - InferenceEngine.cortex_llamacpp - ) - const installedEngines = await this.getInstalledEngines( - InferenceEngine.cortex_llamacpp - ) + const variant = await this.getDefaultEngineVariant('llama-cpp') + if ( + (systemInfo.gpuSetting.vulkan && !variant.variant.includes('vulkan')) || + (systemInfo.gpuSetting.vulkan === false && + variant.variant.includes('vulkan')) + ) { + throw new EngineError('Switch engine.') + } + const installedEngines = await this.getInstalledEngines('llama-cpp') if ( !installedEngines.some( (e) => e.name === variant.variant && e.version === variant.version @@ -341,9 +295,9 @@ export default class JanEngineManagementExtension extends EngineManagementExtens (error instanceof HTTPError && error.response.status === 400) || error instanceof EngineError ) { - const systemInfo = await systemInformation() const variant = await engineVariant(systemInfo.gpuSetting) - await this.setDefaultEngineVariant(InferenceEngine.cortex_llamacpp, { + // TODO: Use correct provider name when moving to llama.cpp extension + await this.setDefaultEngineVariant('llama-cpp', { variant: variant, version: `${CORTEX_ENGINE_VERSION}`, }) @@ -412,7 +366,7 @@ export default class JanEngineManagementExtension extends EngineManagementExtens models.data.map((model) => this.addRemoteModel({ ...model, - engine: engineConfig.engine as InferenceEngine, + engine: engineConfig.engine, model: model.model ?? model.id, }).catch(console.info) ) @@ -428,8 +382,6 @@ export default class JanEngineManagementExtension extends EngineManagementExtens */ migrate = async () => { // Ensure health check is done - await this.queue.onEmpty() - const version = await this.getSetting('version', '0.0.0') const engines = await this.getEngines() if (version < VERSION) { diff --git a/extensions/engine-management-extension/src/node/index.ts b/extensions/engine-management-extension/src/node/index.ts index ae1934b25..ce8d9b274 100644 --- a/extensions/engine-management-extension/src/node/index.ts +++ b/extensions/engine-management-extension/src/node/index.ts @@ -16,12 +16,12 @@ const symlinkEngines = async () => { appResourcePath(), 'shared', 'engines', - 'cortex.llamacpp' + 'llama.cpp' ) const symlinkEnginePath = path.join( getJanDataFolderPath(), 'engines', - 'cortex.llamacpp' + 'llama.cpp' ) const variantFolders = await readdir(sourceEnginePath) const isStandalone = process.platform === 'linux' diff --git a/extensions/engine-management-extension/src/utils.ts b/extensions/engine-management-extension/src/utils.ts index 5e3f01ef7..bc5b09fd3 100644 --- a/extensions/engine-management-extension/src/utils.ts +++ b/extensions/engine-management-extension/src/utils.ts @@ -32,23 +32,25 @@ const gpuRunMode = (settings?: GpuSetting): RunMode => { */ const os = (settings?: GpuSetting): string => { return PLATFORM === 'win32' - ? 'windows-amd64' + ? 'win' : PLATFORM === 'darwin' ? settings?.cpu?.arch === 'arm64' - ? 'mac-arm64' - : 'mac-amd64' - : 'linux-amd64' + ? 'macos-arm64' + : 'macos-x64' + : 'linux' } /** - * The CUDA version that will be set - either '11-7' or '12-0'. + * The CUDA version that will be set - either 'cu12.0' or 'cu11.7'. * @param settings * @returns */ -const cudaVersion = (settings?: GpuSetting): '12-0' | '11-7' | undefined => { +const cudaVersion = ( + settings?: GpuSetting +): 'cu12.0' | 'cu11.7' | undefined => { return settings.gpus?.some((gpu) => gpu.version.includes('12')) - ? '12-0' - : '11-7' + ? 'cu12.0' + : 'cu11.7' } /** @@ -70,9 +72,9 @@ export const engineVariant = async ( const runMode = gpuRunMode(gpuSetting) // Only Nvidia GPUs have addition_information set and activated by default let engineVariant = - !gpuSetting?.vulkan || - !gpuSetting.gpus?.length || - gpuSetting.gpus.some((e) => e.additional_information && e.activated) + !gpuSetting?.vulkan && + (!gpuSetting.gpus?.length || + gpuSetting.gpus.some((e) => e.additional_information && e.activated)) ? [ platform, ...(runMode === RunMode.Cuda @@ -84,15 +86,17 @@ export const engineVariant = async ( : 'noavx', runMode, cudaVersion(gpuSetting), + 'x64', ] : // For cpu only we need to check all available supported instructions [ (gpuSetting.cpu?.instructions ?? ['noavx']).find((e) => instructionBinaryNames.includes(e.toLowerCase()) ) ?? 'noavx', + 'x64', ]), ].filter(Boolean) - : [platform, 'vulkan'] + : [platform, 'vulkan', 'x64'] let engineVariantString = engineVariant.join('-') diff --git a/extensions/hardware-management-extension/package.json b/extensions/hardware-management-extension/package.json index 396404df9..08346b3f2 100644 --- a/extensions/hardware-management-extension/package.json +++ b/extensions/hardware-management-extension/package.json @@ -2,7 +2,7 @@ "name": "@janhq/hardware-management-extension", "productName": "Hardware Management", "version": "1.0.0", - "description": "Manages Better Hardware settings.", + "description": "Manages hardware settings.", "main": "dist/index.js", "node": "dist/node/index.cjs.js", "author": "Jan ", diff --git a/extensions/hardware-management-extension/src/index.ts b/extensions/hardware-management-extension/src/index.ts index edd98a7ae..bd94f3828 100644 --- a/extensions/hardware-management-extension/src/index.ts +++ b/extensions/hardware-management-extension/src/index.ts @@ -1,21 +1,15 @@ import { HardwareManagementExtension, HardwareInformation } from '@janhq/core' import ky, { KyInstance } from 'ky' -import PQueue from 'p-queue' /** * JSONHardwareManagementExtension is a HardwareManagementExtension implementation that provides * functionality for managing engines. */ export default class JSONHardwareManagementExtension extends HardwareManagementExtension { - queue = new PQueue({ concurrency: 1 }) - /** * Called when the extension is loaded. */ - async onLoad() { - // Run Healthcheck - this.queue.add(() => this.healthz()) - } + async onLoad() {} api?: KyInstance /** @@ -23,13 +17,16 @@ export default class JSONHardwareManagementExtension extends HardwareManagementE * @returns */ async apiInstance(): Promise { - if(this.api) return this.api - const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' + if (this.api) return this.api + const apiKey = (await window.core?.api.appToken()) this.api = ky.extend({ prefixUrl: API_URL, - headers: { - Authorization: `Bearer ${apiKey}`, - }, + headers: apiKey + ? { + Authorization: `Bearer ${apiKey}`, + } + : {}, + retry: 10, }) return this.api } @@ -39,45 +36,27 @@ export default class JSONHardwareManagementExtension extends HardwareManagementE */ onUnload() {} - /** - * Do health check on cortex.cpp - * @returns - */ - async healthz(): Promise { - return this.apiInstance().then((api) => - api - .get('healthz', { - retry: { limit: 20, delay: () => 500, methods: ['get'] }, - }) - .then(() => {}) - ) - } - /** * @returns A Promise that resolves to an object of hardware. */ async getHardware(): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get('v1/hardware') - .json() - .then((e) => e) - ) + return this.apiInstance().then((api) => + api + .get('v1/hardware') + .json() + .then((e) => e) ) as Promise } /** * @returns A Promise that resolves to an object of set gpu activate. */ - async setAvtiveGpu(data: { gpus: number[] }): Promise<{ + async setActiveGpu(data: { gpus: number[] }): Promise<{ message: string activated_gpus: number[] }> { - return this.queue.add(() => - this.apiInstance().then((api) => - api.post('v1/hardware/activate', { json: data }).then((e) => e) - ) + return this.apiInstance().then((api) => + api.post('v1/hardware/activate', { json: data }).then((e) => e) ) as Promise<{ message: string activated_gpus: number[] diff --git a/extensions/inference-cortex-extension/bin/version.txt b/extensions/inference-cortex-extension/bin/version.txt index 492b167a6..4014c4f5e 100644 --- a/extensions/inference-cortex-extension/bin/version.txt +++ b/extensions/inference-cortex-extension/bin/version.txt @@ -1 +1 @@ -1.0.12 \ No newline at end of file +1.0.13-rc9 \ No newline at end of file diff --git a/extensions/inference-cortex-extension/download.bat b/extensions/inference-cortex-extension/download.bat index 220c5528b..fe2df6645 100644 --- a/extensions/inference-cortex-extension/download.bat +++ b/extensions/inference-cortex-extension/download.bat @@ -2,39 +2,38 @@ set BIN_PATH=./bin set SHARED_PATH=./../../electron/shared set /p CORTEX_VERSION=<./bin/version.txt -set ENGINE_VERSION=0.1.56 +set ENGINE_VERSION=b5509 -@REM Download cortex.llamacpp binaries -set DOWNLOAD_URL=https://github.com/menloresearch/cortex.llamacpp/releases/download/v%ENGINE_VERSION%/cortex.llamacpp-%ENGINE_VERSION%-windows-amd64 -set CUDA_DOWNLOAD_URL=https://github.com/menloresearch/cortex.llamacpp/releases/download/v%ENGINE_VERSION% -set SUBFOLDERS=windows-amd64-noavx-cuda-12-0 windows-amd64-noavx-cuda-11-7 windows-amd64-avx2-cuda-12-0 windows-amd64-avx2-cuda-11-7 windows-amd64-noavx windows-amd64-avx windows-amd64-avx2 windows-amd64-avx512 windows-amd64-vulkan +@REM Download llama.cpp binaries +set DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/%ENGINE_VERSION%/llama-%ENGINE_VERSION%-bin-win +set DOWNLOAD_GGML_URL=https://github.com/ggml-org/llama.cpp/releases/download/%ENGINE_VERSION%/llama-%ENGINE_VERSION%-bin-win +set CUDA_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/%ENGINE_VERSION% +set SUBFOLDERS=win-noavx-cuda-cu12.0-x64 win-noavx-cuda-cu11.7-x64 win-avx2-cuda-cu12.0-x64 win-avx2-cuda-cu11.7-x64 win-noavx-x64 win-avx-x64 win-avx2-x64 win-avx512-x64 win-vulkan-x64 call .\node_modules\.bin\download -e --strip 1 -o %BIN_PATH% https://github.com/menloresearch/cortex.cpp/releases/download/v%CORTEX_VERSION%/cortex-%CORTEX_VERSION%-windows-amd64.tar.gz -call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-12-0.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-avx2-cuda-12-0/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-11-7.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-avx2-cuda-11-7/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-12-0.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-noavx-cuda-12-0/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-11-7.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-noavx-cuda-11-7/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-noavx/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-avx.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-avx/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-avx2/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-avx512.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-avx512/v%ENGINE_VERSION% -call .\node_modules\.bin\download %DOWNLOAD_URL%-vulkan.tar.gz -e --strip 1 -o %SHARED_PATH%/engines/cortex.llamacpp/windows-amd64-vulkan/v%ENGINE_VERSION% -call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cuda-12-0-windows-amd64.tar.gz -e --strip 1 -o %BIN_PATH% -call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cuda-11-7-windows-amd64.tar.gz -e --strip 1 -o %BIN_PATH% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-cu12.0-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-cuda-cu12.0-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-cuda-cu11.7-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-cuda-cu11.7-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-cu12.0-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-cuda-cu12.0-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-cuda-cu11.7-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-cuda-cu11.7-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-noavx-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-noavx-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx2-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx2-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_URL%-avx512-x64.tar.gz -e --strip 2 -o %SHARED_PATH%/engines/llama.cpp/win-avx512-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %DOWNLOAD_GGML_URL%-vulkan-x64.zip -e --strip 1 -o %SHARED_PATH%/engines/llama.cpp/win-vulkan-x64/%ENGINE_VERSION% +call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu12.0-x64.tar.gz -e --strip 1 -o %BIN_PATH% +call .\node_modules\.bin\download %CUDA_DOWNLOAD_URL%/cudart-llama-bin-win-cu11.7-x64.tar.gz -e --strip 1 -o %BIN_PATH% move %BIN_PATH%\cortex-server-beta.exe %BIN_PATH%\cortex-server.exe del %BIN_PATH%\cortex-beta.exe del %BIN_PATH%\cortex.exe -@REM Loop through each folder and move DLLs (excluding engine.dll) +@REM Loop through each folder and move DLLs for %%F in (%SUBFOLDERS%) do ( - echo Processing folder: %SHARED_PATH%\engines\cortex.llamacpp\%%F\v%ENGINE_VERSION% + echo Processing folder: %SHARED_PATH%\engines\llama.cpp\%%F\%ENGINE_VERSION% - @REM Move all .dll files except engine.dll - for %%D in (%SHARED_PATH%\engines\cortex.llamacpp\%%F\v%ENGINE_VERSION%\*.dll) do ( - if /I not "%%~nxD"=="engine.dll" ( - move "%%D" "%BIN_PATH%" - ) + @REM Move cu*.dll files + for %%D in (%SHARED_PATH%\engines\llama.cpp\%%F\%ENGINE_VERSION%\cu*.dll) do ( + move "%%D" "%BIN_PATH%" ) ) diff --git a/extensions/inference-cortex-extension/download.sh b/extensions/inference-cortex-extension/download.sh index 46fe35c48..834c3315b 100755 --- a/extensions/inference-cortex-extension/download.sh +++ b/extensions/inference-cortex-extension/download.sh @@ -2,10 +2,10 @@ # Read CORTEX_VERSION CORTEX_VERSION=$(cat ./bin/version.txt) -ENGINE_VERSION=0.1.56 +ENGINE_VERSION=b5509 CORTEX_RELEASE_URL="https://github.com/menloresearch/cortex.cpp/releases/download" -ENGINE_DOWNLOAD_URL="https://github.com/menloresearch/cortex.llamacpp/releases/download/v${ENGINE_VERSION}/cortex.llamacpp-${ENGINE_VERSION}" -CUDA_DOWNLOAD_URL="https://github.com/menloresearch/cortex.llamacpp/releases/download/v${ENGINE_VERSION}" +ENGINE_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION}/llama-${ENGINE_VERSION}-bin +CUDA_DOWNLOAD_URL=https://github.com/menloresearch/llama.cpp/releases/download/${ENGINE_VERSION} BIN_PATH=./bin SHARED_PATH="../../electron/shared" # Detect platform @@ -20,17 +20,17 @@ if [ "$OS_TYPE" == "Linux" ]; then chmod +x "./bin/cortex-server" # Download engines for Linux - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-noavx.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-noavx/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-avx.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-avx/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-avx2.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-avx2/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-avx512.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-avx512/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-avx2-cuda-12-0.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-avx2-cuda-12-0/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-avx2-cuda-11-7.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-avx2-cuda-11-7/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-noavx-cuda-12-0.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-noavx-cuda-12-0/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-noavx-cuda-11-7.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-noavx-cuda-11-7/v${ENGINE_VERSION}" 1 - download "${ENGINE_DOWNLOAD_URL}-linux-amd64-vulkan.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-vulkan/v${ENGINE_VERSION}" 1 - download "${CUDA_DOWNLOAD_URL}/cuda-12-0-linux-amd64.tar.gz" -e --strip 1 -o "${BIN_PATH}" 1 - download "${CUDA_DOWNLOAD_URL}/cuda-11-7-linux-amd64.tar.gz" -e --strip 1 -o "${BIN_PATH}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-noavx-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-noavx-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-avx-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-avx-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-avx2-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-avx2-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-avx512-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-avx512-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-avx2-cuda-cu12.0-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-avx2-cuda-cu12.0-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-avx2-cuda-cu11.7-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-avx2-cuda-cu11.7-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-noavx-cuda-cu12.0-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-noavx-cuda-cu12.0-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-noavx-cuda-cu11.7-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-noavx-cuda-cu11.7-x64/${ENGINE_VERSION}" 1 + download "${ENGINE_DOWNLOAD_URL}-linux-vulkan-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/linux-vulkan-x64/${ENGINE_VERSION}" 1 + download "${CUDA_DOWNLOAD_URL}/cudart-llama-bin-linux-cu12.0-x64.tar.gz" -e --strip 1 -o "${BIN_PATH}" 1 + download "${CUDA_DOWNLOAD_URL}/cudart-llama-bin-linux-cu11.7-x64.tar.gz" -e --strip 1 -o "${BIN_PATH}" 1 elif [ "$OS_TYPE" == "Darwin" ]; then # macOS downloads @@ -41,8 +41,8 @@ elif [ "$OS_TYPE" == "Darwin" ]; then chmod +x "./bin/cortex-server" # Download engines for macOS - download "${ENGINE_DOWNLOAD_URL}-mac-arm64.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/mac-arm64/v${ENGINE_VERSION}" - download "${ENGINE_DOWNLOAD_URL}-mac-amd64.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/mac-amd64/v${ENGINE_VERSION}" + download "${ENGINE_DOWNLOAD_URL}-macos-arm64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/macos-arm64/${ENGINE_VERSION}" + download "${ENGINE_DOWNLOAD_URL}-macos-x64.tar.gz" -e --strip 2 -o "${SHARED_PATH}/engines/llama.cpp/macos-x64/${ENGINE_VERSION}" else echo "Unsupported operating system: $OS_TYPE" diff --git a/extensions/inference-cortex-extension/package.json b/extensions/inference-cortex-extension/package.json index 00e7c346e..703b937b9 100644 --- a/extensions/inference-cortex-extension/package.json +++ b/extensions/inference-cortex-extension/package.json @@ -13,8 +13,8 @@ "downloadcortex:linux:darwin": "./download.sh", "downloadcortex:win32": "download.bat", "downloadcortex": "run-script-os", - "build:publish:darwin": "rimraf *.tgz --glob || true && yarn build && yarn downloadcortex && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", - "build:publish:win32:linux": "rimraf *.tgz --glob || true && yarn build && yarn downloadcortex && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", + "build:publish:darwin": "rimraf *.tgz --glob || true && yarn build && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", + "build:publish:win32:linux": "rimraf *.tgz --glob || true && yarn build && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install", "build:publish": "run-script-os" }, "exports": { diff --git a/extensions/inference-cortex-extension/resources/default_settings.json b/extensions/inference-cortex-extension/resources/default_settings.json index 945f32729..451596842 100644 --- a/extensions/inference-cortex-extension/resources/default_settings.json +++ b/extensions/inference-cortex-extension/resources/default_settings.json @@ -1,11 +1,20 @@ [ + { + "key": "auto_unload_models", + "title": "Auto-Unload Old Models", + "description": "Automatically unloads models that are not in use to free up memory. Ensure only one model is loaded at a time.", + "controllerType": "checkbox", + "controllerProps": { + "value": true + } + }, { "key": "cont_batching", "title": "Continuous Batching", "description": "Allows processing prompts in parallel with text generation, which usually improves performance.", "controllerType": "checkbox", "controllerProps": { - "value": true + "value": "" } }, { @@ -14,8 +23,8 @@ "description": "Number of prompts that can be processed simultaneously by the model.", "controllerType": "input", "controllerProps": { - "value": "4", - "placeholder": "4", + "value": "", + "placeholder": "1", "type": "number", "textAlign": "right" } @@ -27,11 +36,22 @@ "controllerType": "input", "controllerProps": { "value": "", - "placeholder": "Number of CPU threads", + "placeholder": "-1 (auto-detect)", "type": "number", "textAlign": "right" } }, + { + "key": "threads_batch", + "title": "Threads (Batch)", + "description": "Number of threads for batch and prompt processing (default: same as Threads).", + "controllerType": "input", + "controllerProps": { + "value": "", + "placeholder": "-1 (same as Threads)", + "type": "number" + } + }, { "key": "flash_attn", "title": "Flash Attention", @@ -41,7 +61,6 @@ "value": true } }, - { "key": "caching_enabled", "title": "Caching", @@ -57,7 +76,7 @@ "description": "Controls memory usage and precision trade-off.", "controllerType": "dropdown", "controllerProps": { - "value": "f16", + "value": "q8_0", "options": [ { "value": "q4_0", @@ -82,5 +101,17 @@ "controllerProps": { "value": true } + }, + { + "key": "hugging-face-access-token", + "title": "Hugging Face Access Token", + "description": "Access tokens programmatically authenticate your identity to the Hugging Face Hub, allowing applications to perform specific actions specified by the scope of permissions granted.", + "controllerType": "input", + "controllerProps": { + "value": "", + "placeholder": "hf_**********************************", + "type": "password", + "inputActions": ["unobscure", "copy"] + } } ] diff --git a/extensions/inference-cortex-extension/rolldown.config.mjs b/extensions/inference-cortex-extension/rolldown.config.mjs index 0e91dfbc1..6a62ddf74 100644 --- a/extensions/inference-cortex-extension/rolldown.config.mjs +++ b/extensions/inference-cortex-extension/rolldown.config.mjs @@ -19,7 +19,7 @@ export default defineConfig([ CORTEX_SOCKET_URL: JSON.stringify( `ws://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` ), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.56'), + CORTEX_ENGINE_VERSION: JSON.stringify('b5509'), }, }, { diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index 7ed51f9c2..a49b1a852 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -8,15 +8,12 @@ import { Model, - executeOnMain, EngineEvent, LocalOAIEngine, - InferenceEngine, extractModelLoadParams, events, ModelEvent, } from '@janhq/core' -import PQueue from 'p-queue' import ky, { KyInstance } from 'ky' /** @@ -30,7 +27,7 @@ enum DownloadTypes { DownloadStarted = 'onFileDownloadStarted', } -export enum Settings { +enum Settings { n_parallel = 'n_parallel', cont_batching = 'cont_batching', caching_enabled = 'caching_enabled', @@ -38,8 +35,12 @@ export enum Settings { cache_type = 'cache_type', use_mmap = 'use_mmap', cpu_threads = 'cpu_threads', + huggingfaceToken = 'hugging-face-access-token', + auto_unload_models = 'auto_unload_models', } +type LoadedModelResponse = { data: { engine: string; id: string }[] } + /** * A class that implements the InferenceExtension interface from the @janhq/core package. * The class provides methods for initializing and stopping a model, and for making inference requests. @@ -48,21 +49,19 @@ export enum Settings { export default class JanInferenceCortexExtension extends LocalOAIEngine { nodeModule: string = 'node' - queue = new PQueue({ concurrency: 1 }) - - provider: string = InferenceEngine.cortex + provider: string = 'cortex' shouldReconnect = true /** Default Engine model load settings */ - n_parallel: number = 4 - cont_batching: boolean = true + n_parallel?: number + cont_batching: boolean = false caching_enabled: boolean = true flash_attn: boolean = true use_mmap: boolean = true - cache_type: string = 'f16' + cache_type: string = 'q8' cpu_threads?: number - + auto_unload_models: boolean = true /** * The URL for making inference requests. */ @@ -81,13 +80,16 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { * @returns */ async apiInstance(): Promise { - if(this.api) return this.api - const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' + if (this.api) return this.api + const apiKey = await window.core?.api.appToken() this.api = ky.extend({ prefixUrl: CORTEX_API_URL, - headers: { - Authorization: `Bearer ${apiKey}`, - }, + headers: apiKey + ? { + Authorization: `Bearer ${apiKey}`, + } + : {}, + retry: 10, }) return this.api } @@ -111,46 +113,84 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { // Register Settings this.registerSettings(SETTINGS) - this.n_parallel = - Number(await this.getSetting(Settings.n_parallel, '4')) ?? 4 - this.cont_batching = await this.getSetting( - Settings.cont_batching, - true - ) + const numParallel = await this.getSetting(Settings.n_parallel, '') + if (numParallel.length > 0 && parseInt(numParallel) > 0) { + this.n_parallel = parseInt(numParallel) + } + if (this.n_parallel && this.n_parallel > 1) + this.cont_batching = await this.getSetting( + Settings.cont_batching, + false + ) this.caching_enabled = await this.getSetting( Settings.caching_enabled, true ) this.flash_attn = await this.getSetting(Settings.flash_attn, true) this.use_mmap = await this.getSetting(Settings.use_mmap, true) - this.cache_type = await this.getSetting(Settings.cache_type, 'f16') + if (this.caching_enabled) + this.cache_type = await this.getSetting(Settings.cache_type, 'q8') + this.auto_unload_models = await this.getSetting( + Settings.auto_unload_models, + true + ) const threads_number = Number( await this.getSetting(Settings.cpu_threads, '') ) + if (!Number.isNaN(threads_number)) this.cpu_threads = threads_number - // Run the process watchdog - // const systemInfo = await systemInformation() - this.queue.add(() => executeOnMain(NODE, 'run')) - this.queue.add(() => this.healthz()) + const huggingfaceToken = await this.getSetting( + Settings.huggingfaceToken, + '' + ) + if (huggingfaceToken) { + this.updateCortexConfig({ huggingface_token: huggingfaceToken }) + } this.subscribeToEvents() window.addEventListener('beforeunload', () => { this.clean() }) + + // Migrate configs + if (!localStorage.getItem('cortex_migration_completed')) { + const config = await this.getCortexConfig() + console.log('Start cortex.cpp migration', config) + if (config && config.huggingface_token) { + this.updateSettings([ + { + key: Settings.huggingfaceToken, + controllerProps: { + value: config.huggingface_token, + }, + }, + ]) + this.updateCortexConfig({ + huggingface_token: config.huggingface_token, + }) + localStorage.setItem('cortex_migration_completed', 'true') + } + } } async onUnload() { console.log('Clean up cortex.cpp services') this.shouldReconnect = false this.clean() - await executeOnMain(NODE, 'dispose') super.onUnload() } + /** + * Subscribe to settings update and make change accordingly + * @param key + * @param value + */ onSettingUpdate(key: string, value: T): void { if (key === Settings.n_parallel && typeof value === 'string') { - this.n_parallel = Number(value) ?? 1 + if (value.length > 0 && parseInt(value) > 0) { + this.n_parallel = parseInt(value) + } } else if (key === Settings.cont_batching && typeof value === 'boolean') { this.cont_batching = value as boolean } else if (key === Settings.caching_enabled && typeof value === 'boolean') { @@ -164,50 +204,81 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { } else if (key === Settings.cpu_threads && typeof value === 'string') { const threads_number = Number(value) if (!Number.isNaN(threads_number)) this.cpu_threads = threads_number + } else if (key === Settings.huggingfaceToken) { + this.updateCortexConfig({ huggingface_token: value }) + } else if (key === Settings.auto_unload_models) { + this.auto_unload_models = value as boolean } } override async loadModel( - model: Model & { file_path?: string } + model: Partial & { + id: string + settings?: object + file_path?: string + }, + abortController: AbortController ): Promise { // Cortex will handle these settings - const { llama_model_path, mmproj, ...settings } = model.settings + const { llama_model_path, mmproj, ...settings } = model.settings ?? {} model.settings = settings - const controller = new AbortController() + const controller = abortController ?? new AbortController() const { signal } = controller this.abortControllers.set(model.id, controller) - return await this.queue.add(() => - this.apiInstance().then((api) => - api - .post('v1/models/start', { - json: { - ...extractModelLoadParams(model.settings), - model: model.id, - engine: - model.engine === InferenceEngine.nitro // Legacy model cache - ? InferenceEngine.cortex_llamacpp - : model.engine, - cont_batching: this.cont_batching, - n_parallel: this.n_parallel, - caching_enabled: this.caching_enabled, - flash_attn: this.flash_attn, - cache_type: this.cache_type, - use_mmap: this.use_mmap, - ...(this.cpu_threads ? { cpu_threads: this.cpu_threads } : {}), - }, - timeout: false, - signal, - }) - .json() - .catch(async (e) => { - throw (await e.response?.json()) ?? e - }) - .finally(() => this.abortControllers.delete(model.id)) - .then() - ) + const loadedModels = await this.activeModels() + + console.log('Loaded models:', loadedModels) + + // This is to avoid loading the same model multiple times + if (loadedModels.some((e: { id: string }) => e.id === model.id)) { + console.log(`Model ${model.id} already loaded`) + return + } + if (this.auto_unload_models) { + // Unload the last used model if it is not the same as the current one + for (const lastUsedModel of loadedModels) { + if (lastUsedModel.id !== model.id) { + console.log(`Unloading last used model: ${lastUsedModel.id}`) + await this.unloadModel(lastUsedModel as Model) + } + } + } + return await this.apiInstance().then((api) => + api + .post('v1/models/start', { + json: { + ...extractModelLoadParams(model.settings), + model: model.id, + engine: + model.engine === 'nitro' // Legacy model cache + ? 'llama-cpp' + : model.engine, + ...(this.n_parallel ? { n_parallel: this.n_parallel } : {}), + ...(this.use_mmap ? { use_mmap: true } : {}), + ...(this.caching_enabled ? { caching_enabled: true } : {}), + ...(this.flash_attn ? { flash_attn: true } : {}), + ...(this.caching_enabled && this.cache_type + ? { cache_type: this.cache_type } + : {}), + ...(this.cpu_threads && this.cpu_threads > 0 + ? { cpu_threads: this.cpu_threads } + : {}), + ...(this.cont_batching && this.n_parallel && this.n_parallel > 1 + ? { cont_batching: this.cont_batching } + : {}), + }, + timeout: false, + signal, + }) + .json() + .catch(async (e) => { + throw (await e.response?.json()) ?? e + }) + .finally(() => this.abortControllers.delete(model.id)) + .then() ) } @@ -216,6 +287,9 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { api .post('v1/models/stop', { json: { model: model.id }, + retry: { + limit: 0, + }, }) .json() .finally(() => { @@ -225,22 +299,18 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { ) } - /** - * Do health check on cortex.cpp - * @returns - */ - private async healthz(): Promise { - return this.apiInstance().then((api) => - api - .get('healthz', { + async activeModels(): Promise<(object & { id: string })[]> { + return await this.apiInstance() + .then((e) => + e.get('inferences/server/models', { retry: { - limit: 20, - delay: () => 500, - methods: ['get'], + limit: 0, // Do not retry }, }) - .then(() => {}) - ) + ) + .then((e) => e.json()) + .then((e) => (e as LoadedModelResponse).data ?? []) + .catch(() => []) } /** @@ -262,80 +332,87 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { }) } + /** + * Update cortex config + * @param body + */ + private async updateCortexConfig(body: { + [key: string]: any + }): Promise { + return this.apiInstance() + .then((api) => api.patch('v1/configs', { json: body }).then(() => {})) + .catch((e) => console.debug(e)) + } + + /** + * Get cortex config + * @param body + */ + private async getCortexConfig(): Promise { + return this.apiInstance() + .then((api) => api.get('v1/configs').json()) + .catch((e) => console.debug(e)) + } + /** * Subscribe to cortex.cpp websocket events */ private subscribeToEvents() { - this.queue.add( - () => - new Promise((resolve) => { - this.socket = new WebSocket(`${CORTEX_SOCKET_URL}/events`) + this.socket = new WebSocket(`${CORTEX_SOCKET_URL}/events`) - this.socket.addEventListener('message', (event) => { - const data = JSON.parse(event.data) + this.socket.addEventListener('message', (event) => { + const data = JSON.parse(event.data) - const transferred = data.task.items.reduce( - (acc: number, cur: any) => acc + cur.downloadedBytes, - 0 - ) - const total = data.task.items.reduce( - (acc: number, cur: any) => acc + cur.bytes, - 0 - ) - const percent = total > 0 ? transferred / total : 0 + const transferred = data.task.items.reduce( + (acc: number, cur: any) => acc + cur.downloadedBytes, + 0 + ) + const total = data.task.items.reduce( + (acc: number, cur: any) => acc + cur.bytes, + 0 + ) + const percent = total > 0 ? transferred / total : 0 - events.emit( - DownloadTypes[data.type as keyof typeof DownloadTypes], - { - modelId: data.task.id, - percent: percent, - size: { - transferred: transferred, - total: total, - }, - downloadType: data.task.type, - } - ) + events.emit(DownloadTypes[data.type as keyof typeof DownloadTypes], { + modelId: data.task.id, + percent: percent, + size: { + transferred: transferred, + total: total, + }, + downloadType: data.task.type, + }) - if (data.task.type === 'Engine') { - events.emit(EngineEvent.OnEngineUpdate, { - type: DownloadTypes[data.type as keyof typeof DownloadTypes], - percent: percent, - id: data.task.id, - }) - } else { - if (data.type === DownloadTypes.DownloadSuccess) { - // Delay for the state update from cortex.cpp - // Just to be sure - setTimeout(() => { - events.emit(ModelEvent.OnModelsUpdate, { - fetch: true, - }) - }, 500) - } - } - }) - - /** - * This is to handle the server segfault issue - */ - this.socket.onclose = (event) => { - console.log('WebSocket closed:', event) - // Notify app to update model running state - events.emit(ModelEvent.OnModelStopped, {}) - - // Reconnect to the /events websocket - if (this.shouldReconnect) { - console.log(`Attempting to reconnect...`) - setTimeout(() => this.subscribeToEvents(), 1000) - } - - // Queue up health check - this.queue.add(() => this.healthz()) - } - - resolve() + if (data.task.type === 'Engine') { + events.emit(EngineEvent.OnEngineUpdate, { + type: DownloadTypes[data.type as keyof typeof DownloadTypes], + percent: percent, + id: data.task.id, }) - ) + } else { + if (data.type === DownloadTypes.DownloadSuccess) { + // Delay for the state update from cortex.cpp + // Just to be sure + setTimeout(() => { + events.emit(ModelEvent.OnModelsUpdate, { + fetch: true, + }) + }, 500) + } + } + }) + + /** + * This is to handle the server segfault issue + */ + this.socket.onclose = (event) => { + // Notify app to update model running state + events.emit(ModelEvent.OnModelStopped, {}) + + // Reconnect to the /events websocket + if (this.shouldReconnect) { + setTimeout(() => this.subscribeToEvents(), 1000) + } + } } } diff --git a/extensions/model-extension/package.json b/extensions/model-extension/package.json index 32ef2f70c..153c22fdf 100644 --- a/extensions/model-extension/package.json +++ b/extensions/model-extension/package.json @@ -2,7 +2,7 @@ "name": "@janhq/model-extension", "productName": "Model Management", "version": "1.0.36", - "description": "Handles model lists, their details, and settings.", + "description": "Manages model operations including listing, importing, updating, and deleting.", "main": "dist/index.js", "author": "Jan ", "license": "AGPL-3.0", diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index fd1e5581d..09d1252ce 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -1,7 +1,6 @@ import { ModelExtension, Model, - InferenceEngine, joinPath, dirName, fs, @@ -12,7 +11,6 @@ import { } from '@janhq/core' import { scanModelsFolder } from './legacy/model-json' import { deleteModelFiles } from './legacy/delete' -import PQueue from 'p-queue' import ky, { KyInstance } from 'ky' /** @@ -31,21 +29,22 @@ type Data = { * A extension for models */ export default class JanModelExtension extends ModelExtension { - queue = new PQueue({ concurrency: 1 }) - api?: KyInstance /** * Get the API instance * @returns */ async apiInstance(): Promise { - if(this.api) return this.api - const apiKey = (await window.core?.api.appToken()) ?? 'cortex.cpp' + if (this.api) return this.api + const apiKey = await window.core?.api.appToken() this.api = ky.extend({ prefixUrl: CORTEX_API_URL, - headers: { - Authorization: `Bearer ${apiKey}`, - }, + headers: apiKey + ? { + Authorization: `Bearer ${apiKey}`, + } + : {}, + retry: 10, }) return this.api } @@ -53,8 +52,6 @@ export default class JanModelExtension extends ModelExtension { * Called when the extension is loaded. */ async onLoad() { - this.queue.add(() => this.healthz()) - this.registerSettings(SETTINGS) // Configure huggingface token if available @@ -97,16 +94,14 @@ export default class JanModelExtension extends ModelExtension { /** * Sending POST to /models/pull/{id} endpoint to pull the model */ - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post('v1/models/pull', { json: { model, id, name }, timeout: false }) - .json() - .catch(async (e) => { - throw (await e.response?.json()) ?? e - }) - .then() - ) + return this.apiInstance().then((api) => + api + .post('v1/models/pull', { json: { model, id, name }, timeout: false }) + .json() + .catch(async (e) => { + throw (await e.response?.json()) ?? e + }) + .then() ) } @@ -120,13 +115,11 @@ export default class JanModelExtension extends ModelExtension { /** * Sending DELETE to /models/pull/{id} endpoint to cancel a model pull */ - return this.queue.add(() => - this.apiInstance().then((api) => - api - .delete('v1/models/pull', { json: { taskId: model } }) - .json() - .then() - ) + return this.apiInstance().then((api) => + api + .delete('v1/models/pull', { json: { taskId: model } }) + .json() + .then() ) } @@ -136,12 +129,8 @@ export default class JanModelExtension extends ModelExtension { * @returns A Promise that resolves when the model is deleted. */ async deleteModel(model: string): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api.delete(`v1/models/${model}`).json().then() - ) - ) + return this.apiInstance() + .then((api) => api.delete(`v1/models/${model}`).json().then()) .catch((e) => console.debug(e)) .finally(async () => { // Delete legacy model files @@ -163,9 +152,7 @@ export default class JanModelExtension extends ModelExtension { * Here we are filtering out the models that are not imported * and are not using llama.cpp engine */ - var toImportModels = legacyModels.filter( - (e) => e.engine === InferenceEngine.nitro - ) + var toImportModels = legacyModels.filter((e) => e.engine === 'nitro') /** * Fetch models from cortex.cpp @@ -241,17 +228,15 @@ export default class JanModelExtension extends ModelExtension { * @param model - The metadata of the model */ async updateModel(model: Partial): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api - .patch(`v1/models/${model.id}`, { - json: { ...model }, - timeout: false, - }) - .json() - .then() - ) + return this.apiInstance() + .then((api) => + api + .patch(`v1/models/${model.id}`, { + json: { ...model }, + timeout: false, + }) + .json() + .then() ) .then(() => this.getModel(model.id)) } @@ -261,13 +246,11 @@ export default class JanModelExtension extends ModelExtension { * @param model - The ID of the model */ async getModel(model: string): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .get(`v1/models/${model}`) - .json() - .then((e) => this.transformModel(e)) - ) + return this.apiInstance().then((api) => + api + .get(`v1/models/${model}`) + .json() + .then((e) => this.transformModel(e)) ) as Promise } @@ -282,17 +265,15 @@ export default class JanModelExtension extends ModelExtension { name?: string, option?: OptionType ): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api - .post('v1/models/import', { - json: { model, modelPath, name, option }, - timeout: false, - }) - .json() - .catch((e) => console.debug(e)) // Ignore error - .then() - ) + return this.apiInstance().then((api) => + api + .post('v1/models/import', { + json: { model, modelPath, name, option }, + timeout: false, + }) + .json() + .catch((e) => console.debug(e)) // Ignore error + .then() ) } @@ -302,12 +283,8 @@ export default class JanModelExtension extends ModelExtension { * @param model */ async getSources(): Promise { - const sources = await this.queue - .add(() => - this.apiInstance().then((api) => - api.get('v1/models/sources').json>() - ) - ) + const sources = await this.apiInstance() + .then((api) => api.get('v1/models/sources').json>()) .then((e) => (typeof e === 'object' ? (e.data as ModelSource[]) : [])) .catch(() => []) return sources.concat( @@ -320,14 +297,12 @@ export default class JanModelExtension extends ModelExtension { * @param model */ async addSource(source: string): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api.post('v1/models/sources', { - json: { - source, - }, - }) - ) + return this.apiInstance().then((api) => + api.post('v1/models/sources', { + json: { + source, + }, + }) ) } @@ -336,15 +311,13 @@ export default class JanModelExtension extends ModelExtension { * @param model */ async deleteSource(source: string): Promise { - return this.queue.add(() => - this.apiInstance().then((api) => - api.delete('v1/models/sources', { - json: { - source, - }, - timeout: false, - }) - ) + return this.apiInstance().then((api) => + api.delete('v1/models/sources', { + json: { + source, + }, + timeout: false, + }) ) } // END - Model Sources @@ -354,10 +327,8 @@ export default class JanModelExtension extends ModelExtension { * @param model */ async isModelLoaded(model: string): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => api.get(`v1/models/status/${model}`)) - ) + return this.apiInstance() + .then((api) => api.get(`v1/models/status/${model}`)) .then((e) => true) .catch(() => false) } @@ -375,12 +346,8 @@ export default class JanModelExtension extends ModelExtension { * @returns */ async fetchModels(): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api.get('v1/models?limit=-1').json>() - ) - ) + return this.apiInstance() + .then((api) => api.get('v1/models?limit=-1').json>()) .then((e) => typeof e === 'object' ? e.data.map((e) => this.transformModel(e)) : [] ) @@ -418,33 +385,9 @@ export default class JanModelExtension extends ModelExtension { private async updateCortexConfig(body: { [key: string]: any }): Promise { - return this.queue - .add(() => - this.apiInstance().then((api) => - api.patch('v1/configs', { json: body }).then(() => {}) - ) - ) - .catch((e) => console.debug(e)) - } - - /** - * Do health check on cortex.cpp - * @returns - */ - private healthz(): Promise { return this.apiInstance() - .then((api) => - api.get('healthz', { - retry: { - limit: 20, - delay: () => 500, - methods: ['get'], - }, - }) - ) - .then(() => { - this.queue.concurrency = Infinity - }) + .then((api) => api.patch('v1/configs', { json: body }).then(() => {})) + .catch((e) => console.debug(e)) } /** @@ -453,25 +396,25 @@ export default class JanModelExtension extends ModelExtension { fetchModelsHub = async () => { const models = await this.fetchModels() - return this.queue.add(() => - this.apiInstance() - .then((api) => - api - .get('v1/models/hub?author=cortexso&tag=cortex.cpp') - .json>() - .then((e) => { - e.data?.forEach((model) => { + return this.apiInstance() + .then((api) => + api + .get('v1/models/hub?author=cortexso&tag=cortex.cpp') + .json>() + .then(async (e) => { + await Promise.all( + e.data?.map((model) => { if ( !models.some( (e) => 'modelSource' in e && e.modelSource === model ) ) - this.addSource(model).catch((e) => console.debug(e)) + return this.addSource(model).catch((e) => console.debug(e)) }) - }) - ) - .catch((e) => console.debug(e)) - ) + ) + }) + ) + .catch((e) => console.debug(e)) } // END: - Private API } diff --git a/extensions/model-extension/src/legacy/model-json.ts b/extensions/model-extension/src/legacy/model-json.ts index e9f0d093b..15ffb6b1f 100644 --- a/extensions/model-extension/src/legacy/model-json.ts +++ b/extensions/model-extension/src/legacy/model-json.ts @@ -1,13 +1,5 @@ -import { InferenceEngine, Model, fs, joinPath } from '@janhq/core' +import { Model, fs, joinPath } from '@janhq/core' //// LEGACY MODEL FOLDER //// -const LocalEngines = [ - InferenceEngine.cortex, - InferenceEngine.cortex_llamacpp, - InferenceEngine.cortex_tensorrtllm, - InferenceEngine.cortex_onnx, - InferenceEngine.nitro_tensorrt_llm, - InferenceEngine.nitro, -] /** * Scan through models folder and return downloaded models * @returns @@ -68,7 +60,7 @@ export const scanModelsFolder = async (): Promise< ) ) if ( - !LocalEngines.includes(model.engine) || + !['cortex', 'llama-cpp', 'nitro'].includes(model.engine) || existFiles.every((exist) => exist) ) return model @@ -86,9 +78,9 @@ export const scanModelsFolder = async (): Promise< file.toLowerCase().endsWith('.engine') // Tensort-LLM ) })?.length >= - (model.engine === InferenceEngine.nitro_tensorrt_llm + (model.engine === 'nitro-tensorrt-llm' ? 1 - : (model.sources?.length ?? 1)) + : model.sources?.length ?? 1) ) }) diff --git a/extensions/yarn.lock b/extensions/yarn.lock index b87f2b047..1a8e45497 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -15,23 +15,6 @@ __metadata: languageName: node linkType: hard -"@anthropic-ai/sdk@npm:^0.9.1": - version: 0.9.1 - resolution: "@anthropic-ai/sdk@npm:0.9.1" - dependencies: - "@types/node": "npm:^18.11.18" - "@types/node-fetch": "npm:^2.6.4" - abort-controller: "npm:^3.0.0" - agentkeepalive: "npm:^4.2.1" - digest-fetch: "npm:^1.3.0" - form-data-encoder: "npm:1.7.2" - formdata-node: "npm:^4.3.2" - node-fetch: "npm:^2.6.7" - web-streams-polyfill: "npm:^3.2.1" - checksum: 10c0/de00ec551a5731a30254e9a3a3ed76f05c0865da6cddf32d4010659f72ca2d25985dff7173af850f92ede1e3695e00105f9689aeb875f0e82dc93f1597be0d05 - languageName: node - linkType: hard - "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0, @babel/code-frame@npm:^7.26.2": version: 7.26.2 resolution: "@babel/code-frame@npm:7.26.2" @@ -652,13 +635,7 @@ __metadata: resolution: "@janhq/assistant-extension@workspace:assistant-extension" dependencies: "@janhq/core": ../../core/package.tgz - "@langchain/community": "npm:0.0.13" - "@types/pdf-parse": "npm:^1.1.4" cpx: "npm:^1.5.0" - hnswlib-node: "npm:^1.4.2" - langchain: "npm:^0.0.214" - node-gyp: "npm:^11.0.0" - pdf-parse: "npm:^1.1.1" rimraf: "npm:^3.0.2" rolldown: "npm:1.0.0-beta.1" run-script-os: "npm:^1.1.6" @@ -673,8 +650,6 @@ __metadata: dependencies: "@janhq/core": ../../core/package.tgz cpx: "npm:^1.5.0" - ky: "npm:^1.7.2" - p-queue: "npm:^8.0.1" rimraf: "npm:^3.0.2" rolldown: "npm:1.0.0-beta.1" ts-loader: "npm:^9.5.0" @@ -684,61 +659,61 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=704042&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=5531aa&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/4c53e86e66a5aa291b56a7257a90b31cd06e624d56a1d114d05b2bed46eaa39da5d9ebc5a86131867b2ebda51089b09bdd8a0ed97f329630e1d35d3463e1ba37 + checksum: 10c0/ee7fe21267cf795dba890781d1e7807a6cb3ecb915ce9ecbd3a8386a2ebc916a8b70a775ce5d9d9f74d2ec29e20b65cea4ef6cdd0ea250a8ff2d5e6bd2237b1e languageName: node linkType: hard @@ -764,7 +739,6 @@ __metadata: resolution: "@janhq/hardware-management-extension@workspace:hardware-management-extension" dependencies: "@janhq/core": ../../core/package.tgz - cpu-instructions: "npm:^0.0.13" cpx: "npm:^1.5.0" ky: "npm:^1.7.2" p-queue: "npm:^8.0.1" @@ -1092,605 +1066,6 @@ __metadata: languageName: node linkType: hard -"@langchain/community@npm:0.0.13": - version: 0.0.13 - resolution: "@langchain/community@npm:0.0.13" - dependencies: - "@langchain/core": "npm:~0.1.5" - "@langchain/openai": "npm:~0.0.9" - flat: "npm:^5.0.2" - langsmith: "npm:~0.0.48" - uuid: "npm:^9.0.0" - zod: "npm:^3.22.3" - peerDependencies: - "@aws-crypto/sha256-js": ^5.0.0 - "@aws-sdk/client-bedrock-runtime": ^3.422.0 - "@aws-sdk/client-dynamodb": ^3.310.0 - "@aws-sdk/client-kendra": ^3.352.0 - "@aws-sdk/client-lambda": ^3.310.0 - "@aws-sdk/client-sagemaker-runtime": ^3.310.0 - "@aws-sdk/client-sfn": ^3.310.0 - "@aws-sdk/credential-provider-node": ^3.388.0 - "@clickhouse/client": ^0.2.5 - "@cloudflare/ai": ^1.0.12 - "@datastax/astra-db-ts": 0.1.2 - "@elastic/elasticsearch": ^8.4.0 - "@getmetal/metal-sdk": "*" - "@getzep/zep-js": ^0.9.0 - "@gomomento/sdk": ^1.51.1 - "@gomomento/sdk-core": ^1.51.1 - "@google-ai/generativelanguage": ^0.2.1 - "@gradientai/nodejs-sdk": ^1.2.0 - "@huggingface/inference": ^2.6.4 - "@mozilla/readability": "*" - "@opensearch-project/opensearch": "*" - "@pinecone-database/pinecone": ^1.1.0 - "@planetscale/database": ^1.8.0 - "@qdrant/js-client-rest": ^1.2.0 - "@raycast/api": ^1.55.2 - "@rockset/client": ^0.9.1 - "@smithy/eventstream-codec": ^2.0.5 - "@smithy/protocol-http": ^3.0.6 - "@smithy/signature-v4": ^2.0.10 - "@smithy/util-utf8": ^2.0.0 - "@supabase/postgrest-js": ^1.1.1 - "@supabase/supabase-js": ^2.10.0 - "@tensorflow-models/universal-sentence-encoder": "*" - "@tensorflow/tfjs-converter": "*" - "@tensorflow/tfjs-core": "*" - "@upstash/redis": ^1.20.6 - "@vercel/kv": ^0.2.3 - "@vercel/postgres": ^0.5.0 - "@writerai/writer-sdk": ^0.40.2 - "@xata.io/client": ^0.28.0 - "@xenova/transformers": ^2.5.4 - "@zilliz/milvus2-sdk-node": ">=2.2.7" - cassandra-driver: ^4.7.2 - chromadb: "*" - closevector-common: 0.1.0-alpha.1 - closevector-node: 0.1.0-alpha.10 - closevector-web: 0.1.0-alpha.16 - cohere-ai: ">=6.0.0" - convex: ^1.3.1 - discord.js: ^14.14.1 - faiss-node: ^0.5.1 - firebase-admin: ^11.9.0 - google-auth-library: ^8.9.0 - googleapis: ^126.0.1 - hnswlib-node: ^1.4.2 - html-to-text: ^9.0.5 - ioredis: ^5.3.2 - jsdom: "*" - llmonitor: ^0.5.9 - lodash: ^4.17.21 - mongodb: ^5.2.0 - mysql2: ^3.3.3 - neo4j-driver: "*" - node-llama-cpp: "*" - pg: ^8.11.0 - pg-copy-streams: ^6.0.5 - pickleparser: ^0.2.1 - portkey-ai: ^0.1.11 - redis: ^4.6.4 - replicate: ^0.18.0 - typeorm: ^0.3.12 - typesense: ^1.5.3 - usearch: ^1.1.1 - vectordb: ^0.1.4 - voy-search: 0.6.2 - weaviate-ts-client: ^1.4.0 - web-auth-library: ^1.0.3 - ws: ^8.14.2 - peerDependenciesMeta: - "@aws-crypto/sha256-js": - optional: true - "@aws-sdk/client-bedrock-runtime": - optional: true - "@aws-sdk/client-dynamodb": - optional: true - "@aws-sdk/client-kendra": - optional: true - "@aws-sdk/client-lambda": - optional: true - "@aws-sdk/client-sagemaker-runtime": - optional: true - "@aws-sdk/client-sfn": - optional: true - "@aws-sdk/credential-provider-node": - optional: true - "@clickhouse/client": - optional: true - "@cloudflare/ai": - optional: true - "@datastax/astra-db-ts": - optional: true - "@elastic/elasticsearch": - optional: true - "@getmetal/metal-sdk": - optional: true - "@getzep/zep-js": - optional: true - "@gomomento/sdk": - optional: true - "@gomomento/sdk-core": - optional: true - "@google-ai/generativelanguage": - optional: true - "@gradientai/nodejs-sdk": - optional: true - "@huggingface/inference": - optional: true - "@mozilla/readability": - optional: true - "@opensearch-project/opensearch": - optional: true - "@pinecone-database/pinecone": - optional: true - "@planetscale/database": - optional: true - "@qdrant/js-client-rest": - optional: true - "@raycast/api": - optional: true - "@rockset/client": - optional: true - "@smithy/eventstream-codec": - optional: true - "@smithy/protocol-http": - optional: true - "@smithy/signature-v4": - optional: true - "@smithy/util-utf8": - optional: true - "@supabase/postgrest-js": - optional: true - "@supabase/supabase-js": - optional: true - "@tensorflow-models/universal-sentence-encoder": - optional: true - "@tensorflow/tfjs-converter": - optional: true - "@tensorflow/tfjs-core": - optional: true - "@upstash/redis": - optional: true - "@vercel/kv": - optional: true - "@vercel/postgres": - optional: true - "@writerai/writer-sdk": - optional: true - "@xata.io/client": - optional: true - "@xenova/transformers": - optional: true - "@zilliz/milvus2-sdk-node": - optional: true - cassandra-driver: - optional: true - chromadb: - optional: true - closevector-common: - optional: true - closevector-node: - optional: true - closevector-web: - optional: true - cohere-ai: - optional: true - convex: - optional: true - discord.js: - optional: true - faiss-node: - optional: true - firebase-admin: - optional: true - google-auth-library: - optional: true - googleapis: - optional: true - hnswlib-node: - optional: true - html-to-text: - optional: true - ioredis: - optional: true - jsdom: - optional: true - llmonitor: - optional: true - lodash: - optional: true - mongodb: - optional: true - mysql2: - optional: true - neo4j-driver: - optional: true - node-llama-cpp: - optional: true - pg: - optional: true - pg-copy-streams: - optional: true - pickleparser: - optional: true - portkey-ai: - optional: true - redis: - optional: true - replicate: - optional: true - typeorm: - optional: true - typesense: - optional: true - usearch: - optional: true - vectordb: - optional: true - voy-search: - optional: true - weaviate-ts-client: - optional: true - web-auth-library: - optional: true - ws: - optional: true - checksum: 10c0/384926b577f9815cd24f2afea175a256d2e4deb09ac767e76daf293689870bef9c4c4bd3909428878b997de6cadd277cf3c585bb3f6f7e0672d57119a31c5b47 - languageName: node - linkType: hard - -"@langchain/community@npm:~0.0.13": - version: 0.0.57 - resolution: "@langchain/community@npm:0.0.57" - dependencies: - "@langchain/core": "npm:~0.1.60" - "@langchain/openai": "npm:~0.0.28" - expr-eval: "npm:^2.0.2" - flat: "npm:^5.0.2" - langsmith: "npm:~0.1.1" - uuid: "npm:^9.0.0" - zod: "npm:^3.22.3" - zod-to-json-schema: "npm:^3.22.5" - peerDependencies: - "@aws-crypto/sha256-js": ^5.0.0 - "@aws-sdk/client-bedrock-agent-runtime": ^3.485.0 - "@aws-sdk/client-bedrock-runtime": ^3.422.0 - "@aws-sdk/client-dynamodb": ^3.310.0 - "@aws-sdk/client-kendra": ^3.352.0 - "@aws-sdk/client-lambda": ^3.310.0 - "@aws-sdk/client-sagemaker-runtime": ^3.310.0 - "@aws-sdk/client-sfn": ^3.310.0 - "@aws-sdk/credential-provider-node": ^3.388.0 - "@azure/search-documents": ^12.0.0 - "@clickhouse/client": ^0.2.5 - "@cloudflare/ai": "*" - "@datastax/astra-db-ts": ^1.0.0 - "@elastic/elasticsearch": ^8.4.0 - "@getmetal/metal-sdk": "*" - "@getzep/zep-js": ^0.9.0 - "@gomomento/sdk": ^1.51.1 - "@gomomento/sdk-core": ^1.51.1 - "@google-ai/generativelanguage": ^0.2.1 - "@gradientai/nodejs-sdk": ^1.2.0 - "@huggingface/inference": ^2.6.4 - "@mlc-ai/web-llm": ^0.2.35 - "@mozilla/readability": "*" - "@neondatabase/serverless": "*" - "@opensearch-project/opensearch": "*" - "@pinecone-database/pinecone": "*" - "@planetscale/database": ^1.8.0 - "@premai/prem-sdk": ^0.3.25 - "@qdrant/js-client-rest": ^1.8.2 - "@raycast/api": ^1.55.2 - "@rockset/client": ^0.9.1 - "@smithy/eventstream-codec": ^2.0.5 - "@smithy/protocol-http": ^3.0.6 - "@smithy/signature-v4": ^2.0.10 - "@smithy/util-utf8": ^2.0.0 - "@supabase/postgrest-js": ^1.1.1 - "@supabase/supabase-js": ^2.10.0 - "@tensorflow-models/universal-sentence-encoder": "*" - "@tensorflow/tfjs-converter": "*" - "@tensorflow/tfjs-core": "*" - "@upstash/redis": ^1.20.6 - "@upstash/vector": ^1.0.7 - "@vercel/kv": ^0.2.3 - "@vercel/postgres": ^0.5.0 - "@writerai/writer-sdk": ^0.40.2 - "@xata.io/client": ^0.28.0 - "@xenova/transformers": ^2.5.4 - "@zilliz/milvus2-sdk-node": ">=2.2.7" - better-sqlite3: ^9.4.0 - cassandra-driver: ^4.7.2 - cborg: ^4.1.1 - chromadb: "*" - closevector-common: 0.1.3 - closevector-node: 0.1.6 - closevector-web: 0.1.6 - cohere-ai: "*" - convex: ^1.3.1 - couchbase: ^4.3.0 - discord.js: ^14.14.1 - dria: ^0.0.3 - duck-duck-scrape: ^2.2.5 - faiss-node: ^0.5.1 - firebase-admin: ^11.9.0 || ^12.0.0 - google-auth-library: ^8.9.0 - googleapis: ^126.0.1 - hnswlib-node: ^3.0.0 - html-to-text: ^9.0.5 - interface-datastore: ^8.2.11 - ioredis: ^5.3.2 - it-all: ^3.0.4 - jsdom: "*" - jsonwebtoken: ^9.0.2 - llmonitor: ^0.5.9 - lodash: ^4.17.21 - lunary: ^0.6.11 - mongodb: ">=5.2.0" - mysql2: ^3.3.3 - neo4j-driver: "*" - node-llama-cpp: "*" - pg: ^8.11.0 - pg-copy-streams: ^6.0.5 - pickleparser: ^0.2.1 - portkey-ai: ^0.1.11 - redis: "*" - replicate: ^0.18.0 - typeorm: ^0.3.12 - typesense: ^1.5.3 - usearch: ^1.1.1 - vectordb: ^0.1.4 - voy-search: 0.6.2 - weaviate-ts-client: "*" - web-auth-library: ^1.0.3 - ws: ^8.14.2 - peerDependenciesMeta: - "@aws-crypto/sha256-js": - optional: true - "@aws-sdk/client-bedrock-agent-runtime": - optional: true - "@aws-sdk/client-bedrock-runtime": - optional: true - "@aws-sdk/client-dynamodb": - optional: true - "@aws-sdk/client-kendra": - optional: true - "@aws-sdk/client-lambda": - optional: true - "@aws-sdk/client-sagemaker-runtime": - optional: true - "@aws-sdk/client-sfn": - optional: true - "@aws-sdk/credential-provider-node": - optional: true - "@azure/search-documents": - optional: true - "@clickhouse/client": - optional: true - "@cloudflare/ai": - optional: true - "@datastax/astra-db-ts": - optional: true - "@elastic/elasticsearch": - optional: true - "@getmetal/metal-sdk": - optional: true - "@getzep/zep-js": - optional: true - "@gomomento/sdk": - optional: true - "@gomomento/sdk-core": - optional: true - "@google-ai/generativelanguage": - optional: true - "@gradientai/nodejs-sdk": - optional: true - "@huggingface/inference": - optional: true - "@mlc-ai/web-llm": - optional: true - "@mozilla/readability": - optional: true - "@neondatabase/serverless": - optional: true - "@opensearch-project/opensearch": - optional: true - "@pinecone-database/pinecone": - optional: true - "@planetscale/database": - optional: true - "@premai/prem-sdk": - optional: true - "@qdrant/js-client-rest": - optional: true - "@raycast/api": - optional: true - "@rockset/client": - optional: true - "@smithy/eventstream-codec": - optional: true - "@smithy/protocol-http": - optional: true - "@smithy/signature-v4": - optional: true - "@smithy/util-utf8": - optional: true - "@supabase/postgrest-js": - optional: true - "@supabase/supabase-js": - optional: true - "@tensorflow-models/universal-sentence-encoder": - optional: true - "@tensorflow/tfjs-converter": - optional: true - "@tensorflow/tfjs-core": - optional: true - "@upstash/redis": - optional: true - "@upstash/vector": - optional: true - "@vercel/kv": - optional: true - "@vercel/postgres": - optional: true - "@writerai/writer-sdk": - optional: true - "@xata.io/client": - optional: true - "@xenova/transformers": - optional: true - "@zilliz/milvus2-sdk-node": - optional: true - better-sqlite3: - optional: true - cassandra-driver: - optional: true - cborg: - optional: true - chromadb: - optional: true - closevector-common: - optional: true - closevector-node: - optional: true - closevector-web: - optional: true - cohere-ai: - optional: true - convex: - optional: true - couchbase: - optional: true - discord.js: - optional: true - dria: - optional: true - duck-duck-scrape: - optional: true - faiss-node: - optional: true - firebase-admin: - optional: true - google-auth-library: - optional: true - googleapis: - optional: true - hnswlib-node: - optional: true - html-to-text: - optional: true - interface-datastore: - optional: true - ioredis: - optional: true - it-all: - optional: true - jsdom: - optional: true - jsonwebtoken: - optional: true - llmonitor: - optional: true - lodash: - optional: true - lunary: - optional: true - mongodb: - optional: true - mysql2: - optional: true - neo4j-driver: - optional: true - node-llama-cpp: - optional: true - pg: - optional: true - pg-copy-streams: - optional: true - pickleparser: - optional: true - portkey-ai: - optional: true - redis: - optional: true - replicate: - optional: true - typeorm: - optional: true - typesense: - optional: true - usearch: - optional: true - vectordb: - optional: true - voy-search: - optional: true - weaviate-ts-client: - optional: true - web-auth-library: - optional: true - ws: - optional: true - checksum: 10c0/7d42470c5c42d97c9e9b4925de3fa8a558d785e2c96c347d4a48f97f6396c64e54f3730340c2d262e09440fc664ef44496a9ce09894e75b2e4362017e6bc478c - languageName: node - linkType: hard - -"@langchain/core@npm:>0.1.56 <0.3.0": - version: 0.2.36 - resolution: "@langchain/core@npm:0.2.36" - dependencies: - ansi-styles: "npm:^5.0.0" - camelcase: "npm:6" - decamelize: "npm:1.2.0" - js-tiktoken: "npm:^1.0.12" - langsmith: "npm:^0.1.56-rc.1" - mustache: "npm:^4.2.0" - p-queue: "npm:^6.6.2" - p-retry: "npm:4" - uuid: "npm:^10.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.22.3" - checksum: 10c0/ff49119a295e9605258c5e4fbb0628824f6532937f4f5ca4d141f2f5a10856f3d0890be0fc12b0ebba7ab84d9e66839094780d8e6299f46ddce8751fd8a96d1f - languageName: node - linkType: hard - -"@langchain/core@npm:~0.1.5, @langchain/core@npm:~0.1.60": - version: 0.1.63 - resolution: "@langchain/core@npm:0.1.63" - dependencies: - ansi-styles: "npm:^5.0.0" - camelcase: "npm:6" - decamelize: "npm:1.2.0" - js-tiktoken: "npm:^1.0.12" - langsmith: "npm:~0.1.7" - ml-distance: "npm:^4.0.0" - mustache: "npm:^4.2.0" - p-queue: "npm:^6.6.2" - p-retry: "npm:4" - uuid: "npm:^9.0.0" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.22.3" - checksum: 10c0/44f8b0846439f55e2137fe150275682978b91cf4c2516320c1a17b3180558c3fb709eafc974d5b23dd356006bed542390f52bc7b5a10f8f4284836556a921614 - languageName: node - linkType: hard - -"@langchain/openai@npm:~0.0.28, @langchain/openai@npm:~0.0.9": - version: 0.0.34 - resolution: "@langchain/openai@npm:0.0.34" - dependencies: - "@langchain/core": "npm:>0.1.56 <0.3.0" - js-tiktoken: "npm:^1.0.12" - openai: "npm:^4.41.1" - zod: "npm:^3.22.4" - zod-to-json-schema: "npm:^3.22.3" - checksum: 10c0/16a909b23340d680f3b1de09a56868b0cf00123e992412287987d39e5bf92c40c4247c7113b491f99a98623d12f1c578c3e874a4011d0af5e57212637ceb6328 - languageName: node - linkType: hard - "@napi-rs/wasm-runtime@npm:^0.2.4": version: 0.2.6 resolution: "@napi-rs/wasm-runtime@npm:0.2.6" @@ -2180,16 +1555,6 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:^2.6.4": - version: 2.6.12 - resolution: "@types/node-fetch@npm:2.6.12" - dependencies: - "@types/node": "npm:*" - form-data: "npm:^4.0.0" - checksum: 10c0/7693acad5499b7df2d1727d46cff092a63896dc04645f36b973dd6dd754a59a7faba76fcb777bdaa35d80625c6a9dd7257cca9c401a4bab03b04480cda7fd1af - languageName: node - linkType: hard - "@types/node@npm:*": version: 22.10.2 resolution: "@types/node@npm:22.10.2" @@ -2199,15 +1564,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18": - version: 18.19.68 - resolution: "@types/node@npm:18.19.68" - dependencies: - undici-types: "npm:~5.26.4" - checksum: 10c0/8c7f01be218c6e3c1e643173662af27e9a2b568f36c0fe83e4295cf7674fe2a0abb4a1c5d7c7abd3345b9114581387dfd3f14b6d0338daebdce9273cd7ba59ab - languageName: node - linkType: hard - "@types/node@npm:^20.11.4": version: 20.17.10 resolution: "@types/node@npm:20.17.10" @@ -2224,13 +1580,6 @@ __metadata: languageName: node linkType: hard -"@types/pdf-parse@npm:^1.1.4": - version: 1.1.4 - resolution: "@types/pdf-parse@npm:1.1.4" - checksum: 10c0/1192e0a40bae935428a7f02a56d1313474f0d735820824e1d00f06a0330cf89cc18e34d63864202ae997fbd7806952fa68a47a1f0cbc81f8d03000f942543d5c - languageName: node - linkType: hard - "@types/responselike@npm:^1.0.0": version: 1.0.3 resolution: "@types/responselike@npm:1.0.3" @@ -2240,13 +1589,6 @@ __metadata: languageName: node linkType: hard -"@types/retry@npm:0.12.0": - version: 0.12.0 - resolution: "@types/retry@npm:0.12.0" - checksum: 10c0/7c5c9086369826f569b83a4683661557cab1361bac0897a1cefa1a915ff739acd10ca0d62b01071046fe3f5a3f7f2aec80785fe283b75602dc6726781ea3e328 - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -2261,20 +1603,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "@types/uuid@npm:10.0.0" - checksum: 10c0/9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 - languageName: node - linkType: hard - -"@types/uuid@npm:^9.0.1": - version: 9.0.8 - resolution: "@types/uuid@npm:9.0.8" - checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489 - languageName: node - linkType: hard - "@types/yargs-parser@npm:*": version: 21.0.3 resolution: "@types/yargs-parser@npm:21.0.3" @@ -2460,15 +1788,6 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 - languageName: node - linkType: hard - "agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": version: 7.1.3 resolution: "agent-base@npm:7.1.3" @@ -2476,15 +1795,6 @@ __metadata: languageName: node linkType: hard -"agentkeepalive@npm:^4.2.1": - version: 4.5.0 - resolution: "agentkeepalive@npm:4.5.0" - dependencies: - humanize-ms: "npm:^1.2.1" - checksum: 10c0/394ea19f9710f230722996e156607f48fdf3a345133b0b1823244b7989426c16019a428b56c82d3eabef616e938812981d9009f4792ecc66bd6a59e991c62612 - languageName: node - linkType: hard - "ansi-escapes@npm:^4.2.1": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" @@ -2560,13 +1870,6 @@ __metadata: languageName: node linkType: hard -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e - languageName: node - linkType: hard - "arr-diff@npm:^2.0.0": version: 2.0.0 resolution: "arr-diff@npm:2.0.0" @@ -2646,13 +1949,6 @@ __metadata: languageName: node linkType: hard -"asynckit@npm:^0.4.0": - version: 0.4.0 - resolution: "asynckit@npm:0.4.0" - checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d - languageName: node - linkType: hard - "atob@npm:^2.1.2": version: 2.1.2 resolution: "atob@npm:2.1.2" @@ -2758,14 +2054,7 @@ __metadata: languageName: node linkType: hard -"base-64@npm:^0.1.0": - version: 0.1.0 - resolution: "base-64@npm:0.1.0" - checksum: 10c0/fe0dcf076e823f04db7ee9b02495be08a91c445fbc6db03cb9913be9680e2fcc0af8b74459041fe08ad16800b1f65a549501d8f08696a8a6d32880789b7de69d - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1, base64-js@npm:^1.5.1": +"base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf @@ -2794,20 +2083,6 @@ __metadata: languageName: node linkType: hard -"binary-extensions@npm:^2.2.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 10c0/75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - -"binary-search@npm:^1.3.5": - version: 1.3.6 - resolution: "binary-search@npm:1.3.6" - checksum: 10c0/786a770e3411cf563c9c7829e2854d79583a207b8faaa5022f93352893e1d06035ae5d80de1b168dcbd9d346fdb0dd2e3d7fcdf309b3a63dc027e92624da32a0 - languageName: node - linkType: hard - "bindings@npm:^1.5.0": version: 1.5.0 resolution: "bindings@npm:1.5.0" @@ -2827,17 +2102,6 @@ __metadata: languageName: node linkType: hard -"bl@npm:^4.0.3": - version: 4.1.0 - resolution: "bl@npm:4.1.0" - dependencies: - buffer: "npm:^5.5.0" - inherits: "npm:^2.0.4" - readable-stream: "npm:^3.4.0" - checksum: 10c0/02847e1d2cb089c9dc6958add42e3cdeaf07d13f575973963335ac0fdece563a50ac770ac4c8fa06492d2dd276f6cc3b7f08c7cd9c7a7ad0f8d388b2a28def5f - languageName: node - linkType: hard - "brace-expansion@npm:^1.1.7": version: 1.1.11 resolution: "brace-expansion@npm:1.1.11" @@ -2965,7 +2229,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1, buffer@npm:^5.5.0": +"buffer@npm:^5.2.1": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -3036,13 +2300,6 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:6, camelcase@npm:^6.2.0": - version: 6.3.0 - resolution: "camelcase@npm:6.3.0" - checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 - languageName: node - linkType: hard - "camelcase@npm:^2.0.0": version: 2.1.1 resolution: "camelcase@npm:2.1.1" @@ -3057,6 +2314,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10c0/0d701658219bd3116d12da3eab31acddb3f9440790c0792e0d398f0a520a6a4058018e546862b6fba89d7ae990efaeb97da71e1913e9ebf5a8b5621a3d55c710 + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001688": version: 1.0.30001690 resolution: "caniuse-lite@npm:1.0.30001690" @@ -3106,13 +2370,6 @@ __metadata: languageName: node linkType: hard -"charenc@npm:0.0.2": - version: 0.0.2 - resolution: "charenc@npm:0.0.2" - checksum: 10c0/a45ec39363a16799d0f9365c8dd0c78e711415113c6f14787a22462ef451f5013efae8a28f1c058f81fc01f2a6a16955f7a5fd0cd56247ce94a45349c89877d8 - languageName: node - linkType: hard - "check-error@npm:^2.1.1": version: 2.1.1 resolution: "check-error@npm:2.1.1" @@ -3140,13 +2397,6 @@ __metadata: languageName: node linkType: hard -"chownr@npm:^1.1.1": - version: 1.1.4 - resolution: "chownr@npm:1.1.4" - checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db - languageName: node - linkType: hard - "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -3231,22 +2481,6 @@ __metadata: languageName: node linkType: hard -"combined-stream@npm:^1.0.8": - version: 1.0.8 - resolution: "combined-stream@npm:1.0.8" - dependencies: - delayed-stream: "npm:~1.0.0" - checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 - languageName: node - linkType: hard - -"commander@npm:^10.0.1": - version: 10.0.1 - resolution: "commander@npm:10.0.1" - checksum: 10c0/53f33d8927758a911094adadda4b2cbac111a5b377d8706700587650fd8f45b0bbe336de4b5c3fe47fd61f420a3d9bd452b6e0e6e5600a7e74d7bf0174f6efe3 - languageName: node - linkType: hard - "commander@npm:^2.8.1": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -3315,20 +2549,6 @@ __metadata: languageName: node linkType: hard -"cpu-instructions@npm:^0.0.13": - version: 0.0.13 - resolution: "cpu-instructions@npm:0.0.13" - dependencies: - bindings: "npm:^1.5.0" - node-addon-api: "npm:^8.0.0" - node-gyp-build: "npm:^4.8.1" - prebuildify: "npm:^6.0.1" - peerDependencies: - node-gyp: ^10 - checksum: 10c0/60860df7eee1d4f9c7cd710e634c9f0fdb461a5e90ce2a563e691ed23a290eeb9957562bf5149bf72dea8d2ddaacfdc8b0a824754faae8bc397dccc2902839a5 - languageName: node - linkType: hard - "cpx@npm:^1.5.0": version: 1.5.0 resolution: "cpx@npm:1.5.0" @@ -3378,13 +2598,6 @@ __metadata: languageName: node linkType: hard -"crypt@npm:0.0.2": - version: 0.0.2 - resolution: "crypt@npm:0.0.2" - checksum: 10c0/adbf263441dd801665d5425f044647533f39f4612544071b1471962209d235042fb703c27eea2795c7c53e1dfc242405173003f83cf4f4761a633d11f9653f18 - languageName: node - linkType: hard - "currently-unhandled@npm:^0.4.1": version: 0.4.1 resolution: "currently-unhandled@npm:0.4.1" @@ -3415,16 +2628,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^3.1.0": - version: 3.2.7 - resolution: "debug@npm:3.2.7" - dependencies: - ms: "npm:^2.1.1" - checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a - languageName: node - linkType: hard - -"decamelize@npm:1.2.0, decamelize@npm:^1.1.2": +"decamelize@npm:^1.1.2": version: 1.2.0 resolution: "decamelize@npm:1.2.0" checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 @@ -3564,13 +2768,6 @@ __metadata: languageName: node linkType: hard -"delayed-stream@npm:~1.0.0": - version: 1.0.0 - resolution: "delayed-stream@npm:1.0.0" - checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 - languageName: node - linkType: hard - "detect-newline@npm:^3.0.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" @@ -3585,16 +2782,6 @@ __metadata: languageName: node linkType: hard -"digest-fetch@npm:^1.3.0": - version: 1.3.0 - resolution: "digest-fetch@npm:1.3.0" - dependencies: - base-64: "npm:^0.1.0" - md5: "npm:^2.3.0" - checksum: 10c0/0fb389e33b9c6baf5e6a9ed287aa9d0d8b373d59b49d49c62c261e1ab24eaaf1d5aea3a105c1b31ba4a23e29e129365d839ce4c5974fa004a85d1a4568bc3585 - languageName: node - linkType: hard - "download-cli@npm:^1.1.1": version: 1.1.1 resolution: "download-cli@npm:1.1.1" @@ -3695,7 +2882,7 @@ __metadata: languageName: node linkType: hard -"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": +"end-of-stream@npm:^1.0.0": version: 1.4.4 resolution: "end-of-stream@npm:1.4.4" dependencies: @@ -3870,20 +3057,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b - languageName: node - linkType: hard - -"eventemitter3@npm:^4.0.4": - version: 4.0.7 - resolution: "eventemitter3@npm:4.0.7" - checksum: 10c0/5f6d97cbcbac47be798e6355e3a7639a84ee1f7d9b199a07017f1d2f1e2fe236004d14fa5dfaeba661f94ea57805385e326236a6debbc7145c8877fbc0297c6b - languageName: node - linkType: hard - "eventemitter3@npm:^5.0.1": version: 5.0.1 resolution: "eventemitter3@npm:5.0.1" @@ -3975,13 +3148,6 @@ __metadata: languageName: node linkType: hard -"expr-eval@npm:^2.0.2": - version: 2.0.2 - resolution: "expr-eval@npm:2.0.2" - checksum: 10c0/642f112ff28ea34574c595c3ad73ccd8e638498879a4dd28620c4dabebab2e11987a851266ba81883dae85a5800e0c93b3d06f81718b71a215f831534646e4f2 - languageName: node - linkType: hard - "ext-list@npm:^2.0.0": version: 2.2.2 resolution: "ext-list@npm:2.2.2" @@ -4200,15 +3366,6 @@ __metadata: languageName: node linkType: hard -"flat@npm:^5.0.2": - version: 5.0.2 - resolution: "flat@npm:5.0.2" - bin: - flat: cli.js - checksum: 10c0/f178b13482f0cd80c7fede05f4d10585b1f2fdebf26e12edc138e32d3150c6ea6482b7f12813a1091143bad52bb6d3596bca51a162257a21163c0ff438baa5fe - languageName: node - linkType: hard - "for-in@npm:^1.0.1, for-in@npm:^1.0.2": version: 1.0.2 resolution: "for-in@npm:1.0.2" @@ -4235,34 +3392,6 @@ __metadata: languageName: node linkType: hard -"form-data-encoder@npm:1.7.2": - version: 1.7.2 - resolution: "form-data-encoder@npm:1.7.2" - checksum: 10c0/56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 - languageName: node - linkType: hard - -"form-data@npm:^4.0.0": - version: 4.0.1 - resolution: "form-data@npm:4.0.1" - dependencies: - asynckit: "npm:^0.4.0" - combined-stream: "npm:^1.0.8" - mime-types: "npm:^2.1.12" - checksum: 10c0/bb102d570be8592c23f4ea72d7df9daa50c7792eb0cf1c5d7e506c1706e7426a4e4ae48a35b109e91c85f1c0ec63774a21ae252b66f4eb981cb8efef7d0463c8 - languageName: node - linkType: hard - -"formdata-node@npm:^4.3.2": - version: 4.4.1 - resolution: "formdata-node@npm:4.4.1" - dependencies: - node-domexception: "npm:1.0.0" - web-streams-polyfill: "npm:4.0.0-beta.3" - checksum: 10c0/74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a - languageName: node - linkType: hard - "fragment-cache@npm:^0.2.1": version: 0.2.1 resolution: "fragment-cache@npm:0.2.1" @@ -4575,17 +3704,6 @@ __metadata: languageName: node linkType: hard -"hnswlib-node@npm:^1.4.2": - version: 1.4.2 - resolution: "hnswlib-node@npm:1.4.2" - dependencies: - bindings: "npm:^1.5.0" - node-addon-api: "npm:^6.0.0" - node-gyp: "npm:latest" - checksum: 10c0/562946c2bfa803080f8b5f67e9e7dd519a3ce5bf8de311bb247fd141eaa43a064d70d7ad5f6f85f98f1d78d6970e4ae2912d0a73039f7968328605c14184f953 - languageName: node - linkType: hard - "hosted-git-info@npm:^2.1.4": version: 2.8.9 resolution: "hosted-git-info@npm:2.8.9" @@ -4634,15 +3752,6 @@ __metadata: languageName: node linkType: hard -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a - languageName: node - linkType: hard - "iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -4697,7 +3806,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -4730,13 +3839,6 @@ __metadata: languageName: node linkType: hard -"is-any-array@npm:^2.0.0": - version: 2.0.1 - resolution: "is-any-array@npm:2.0.1" - checksum: 10c0/f9807458a51e63ca1ac27fd6f3a3ace8200f077094e00d9b05b24cfbc9d5594d586d6ecf3416271f26939d5cb93fc52ca869cb5744e77318c3f53ec70b08d61f - languageName: node - linkType: hard - "is-arrayish@npm:^0.2.1": version: 0.2.1 resolution: "is-arrayish@npm:0.2.1" @@ -4753,7 +3855,7 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^1.1.5, is-buffer@npm:~1.1.6": +"is-buffer@npm:^1.1.5": version: 1.1.6 resolution: "is-buffer@npm:1.1.6" checksum: 10c0/ae18aa0b6e113d6c490ad1db5e8df9bdb57758382b313f5a22c9c61084875c6396d50bbf49315f5b1926d142d74dfb8d31b40d993a383e0a158b15fea7a82234 @@ -5556,15 +4658,6 @@ __metadata: languageName: node linkType: hard -"js-tiktoken@npm:^1.0.12, js-tiktoken@npm:^1.0.7": - version: 1.0.16 - resolution: "js-tiktoken@npm:1.0.16" - dependencies: - base64-js: "npm:^1.5.1" - checksum: 10c0/9c3b7ff9b675334eb939f97fb83da31bb499b2a34cc7da42ee7c1a72f4286b40d2c78c7dca375eece5cc20c35a00f2b6b343387fa14f2472e615cf09b755cfdd - languageName: node - linkType: hard - "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -5584,17 +4677,6 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f - languageName: node - linkType: hard - "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -5627,13 +4709,6 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.1": - version: 5.0.1 - resolution: "jsonpointer@npm:5.0.1" - checksum: 10c0/89929e58b400fcb96928c0504fcf4fc3f919d81e9543ceb055df125538470ee25290bb4984251e172e6ef8fcc55761eb998c118da763a82051ad89d4cb073fe7 - languageName: node - linkType: hard - "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": version: 3.2.2 resolution: "kind-of@npm:3.2.2" @@ -5673,227 +4748,6 @@ __metadata: languageName: node linkType: hard -"langchain@npm:^0.0.214": - version: 0.0.214 - resolution: "langchain@npm:0.0.214" - dependencies: - "@anthropic-ai/sdk": "npm:^0.9.1" - "@langchain/community": "npm:~0.0.13" - "@langchain/core": "npm:~0.1.5" - "@langchain/openai": "npm:~0.0.9" - binary-extensions: "npm:^2.2.0" - expr-eval: "npm:^2.0.2" - js-tiktoken: "npm:^1.0.7" - js-yaml: "npm:^4.1.0" - jsonpointer: "npm:^5.0.1" - langchainhub: "npm:~0.0.6" - langsmith: "npm:~0.0.48" - ml-distance: "npm:^4.0.0" - openapi-types: "npm:^12.1.3" - p-retry: "npm:4" - uuid: "npm:^9.0.0" - yaml: "npm:^2.2.1" - zod: "npm:^3.22.3" - zod-to-json-schema: "npm:3.20.3" - peerDependencies: - "@aws-sdk/client-s3": ^3.310.0 - "@aws-sdk/client-sagemaker-runtime": ^3.310.0 - "@aws-sdk/client-sfn": ^3.310.0 - "@aws-sdk/credential-provider-node": ^3.388.0 - "@azure/storage-blob": ^12.15.0 - "@gomomento/sdk": ^1.51.1 - "@gomomento/sdk-core": ^1.51.1 - "@gomomento/sdk-web": ^1.51.1 - "@google-ai/generativelanguage": ^0.2.1 - "@google-cloud/storage": ^6.10.1 - "@notionhq/client": ^2.2.10 - "@pinecone-database/pinecone": ^1.1.0 - "@supabase/supabase-js": ^2.10.0 - "@vercel/kv": ^0.2.3 - "@xata.io/client": ^0.28.0 - apify-client: ^2.7.1 - assemblyai: ^4.0.0 - axios: "*" - cheerio: ^1.0.0-rc.12 - chromadb: "*" - convex: ^1.3.1 - d3-dsv: ^2.0.0 - epub2: ^3.0.1 - fast-xml-parser: ^4.2.7 - google-auth-library: ^8.9.0 - googleapis: ^126.0.1 - html-to-text: ^9.0.5 - ignore: ^5.2.0 - ioredis: ^5.3.2 - jsdom: "*" - mammoth: ^1.6.0 - mongodb: ^5.2.0 - node-llama-cpp: "*" - notion-to-md: ^3.1.0 - officeparser: ^4.0.4 - pdf-parse: 1.1.1 - peggy: ^3.0.2 - playwright: ^1.32.1 - puppeteer: ^19.7.2 - pyodide: ^0.24.1 - redis: ^4.6.4 - sonix-speech-recognition: ^2.1.1 - srt-parser-2: ^1.2.2 - typeorm: ^0.3.12 - vectordb: ^0.1.4 - weaviate-ts-client: ^1.4.0 - web-auth-library: ^1.0.3 - ws: ^8.14.2 - youtube-transcript: ^1.0.6 - youtubei.js: ^5.8.0 - peerDependenciesMeta: - "@aws-sdk/client-s3": - optional: true - "@aws-sdk/client-sagemaker-runtime": - optional: true - "@aws-sdk/client-sfn": - optional: true - "@aws-sdk/credential-provider-node": - optional: true - "@azure/storage-blob": - optional: true - "@gomomento/sdk": - optional: true - "@gomomento/sdk-core": - optional: true - "@gomomento/sdk-web": - optional: true - "@google-ai/generativelanguage": - optional: true - "@google-cloud/storage": - optional: true - "@notionhq/client": - optional: true - "@pinecone-database/pinecone": - optional: true - "@supabase/supabase-js": - optional: true - "@vercel/kv": - optional: true - "@xata.io/client": - optional: true - apify-client: - optional: true - assemblyai: - optional: true - axios: - optional: true - cheerio: - optional: true - chromadb: - optional: true - convex: - optional: true - d3-dsv: - optional: true - epub2: - optional: true - faiss-node: - optional: true - fast-xml-parser: - optional: true - google-auth-library: - optional: true - googleapis: - optional: true - html-to-text: - optional: true - ignore: - optional: true - ioredis: - optional: true - jsdom: - optional: true - mammoth: - optional: true - mongodb: - optional: true - node-llama-cpp: - optional: true - notion-to-md: - optional: true - officeparser: - optional: true - pdf-parse: - optional: true - peggy: - optional: true - playwright: - optional: true - puppeteer: - optional: true - pyodide: - optional: true - redis: - optional: true - sonix-speech-recognition: - optional: true - srt-parser-2: - optional: true - typeorm: - optional: true - vectordb: - optional: true - weaviate-ts-client: - optional: true - web-auth-library: - optional: true - ws: - optional: true - youtube-transcript: - optional: true - youtubei.js: - optional: true - checksum: 10c0/4c70acd1d7ad8b999a7fa2b86bba15ebe4de74339cba0028141e03f8d38f08f1ee5369a707a5597044321d36e4bafe72eed95c04ebb410aa7e881e833d87c201 - languageName: node - linkType: hard - -"langchainhub@npm:~0.0.6": - version: 0.0.11 - resolution: "langchainhub@npm:0.0.11" - checksum: 10c0/6ed781b9e8165bfb5cedc822a25bc70df0f3fc02662061d19a5e2044243cfae797857a05d139de8f326539b1f3fe03f2662060eed82669e405181f1f0f435c47 - languageName: node - linkType: hard - -"langsmith@npm:^0.1.56-rc.1, langsmith@npm:~0.1.1, langsmith@npm:~0.1.7": - version: 0.1.68 - resolution: "langsmith@npm:0.1.68" - dependencies: - "@types/uuid": "npm:^10.0.0" - commander: "npm:^10.0.1" - p-queue: "npm:^6.6.2" - p-retry: "npm:4" - semver: "npm:^7.6.3" - uuid: "npm:^10.0.0" - peerDependencies: - openai: "*" - peerDependenciesMeta: - openai: - optional: true - checksum: 10c0/ec5f70155ce7c95f2326a3d38b313c404debe954890c1623aa07be3db25aa3d7f5381267ed5f456a2bd814ab7bdebdf467ad5dc89bbc78bcc74f5a60e93c09c1 - languageName: node - linkType: hard - -"langsmith@npm:~0.0.48": - version: 0.0.70 - resolution: "langsmith@npm:0.0.70" - dependencies: - "@types/uuid": "npm:^9.0.1" - commander: "npm:^10.0.1" - p-queue: "npm:^6.6.2" - p-retry: "npm:4" - uuid: "npm:^9.0.0" - bin: - langsmith: dist/cli/main.cjs - checksum: 10c0/54e35456db842630e9a3b93b372636bf5f0ffce0af1711dd647095d9359650cfac7d5f1a0e13a1b7d691ae1dd4884d9e2e85373883542da50e1339ad3ccd1f34 - languageName: node - linkType: hard - "layerr@npm:^3.0.0": version: 3.0.0 resolution: "layerr@npm:3.0.0" @@ -6076,17 +4930,6 @@ __metadata: languageName: node linkType: hard -"md5@npm:^2.3.0": - version: 2.3.0 - resolution: "md5@npm:2.3.0" - dependencies: - charenc: "npm:0.0.2" - crypt: "npm:0.0.2" - is-buffer: "npm:~1.1.6" - checksum: 10c0/14a21d597d92e5b738255fbe7fe379905b8cb97e0a49d44a20b58526a646ec5518c337b817ce0094ca94d3e81a3313879c4c7b510d250c282d53afbbdede9110 - languageName: node - linkType: hard - "meow@npm:^3.3.0": version: 3.7.0 resolution: "meow@npm:3.7.0" @@ -6164,13 +5007,6 @@ __metadata: languageName: node linkType: hard -"mime-db@npm:1.52.0": - version: 1.52.0 - resolution: "mime-db@npm:1.52.0" - checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa - languageName: node - linkType: hard - "mime-db@npm:^1.28.0": version: 1.53.0 resolution: "mime-db@npm:1.53.0" @@ -6178,15 +5014,6 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.12": - version: 2.1.35 - resolution: "mime-types@npm:2.1.35" - dependencies: - mime-db: "npm:1.52.0" - checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 - languageName: node - linkType: hard - "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -6228,7 +5055,7 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.1.0, minimist@npm:^1.1.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.1.0, minimist@npm:^1.1.3, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 @@ -6322,13 +5149,6 @@ __metadata: languageName: node linkType: hard -"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": - version: 0.5.3 - resolution: "mkdirp-classic@npm:0.5.3" - checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 - languageName: node - linkType: hard - "mkdirp@npm:^0.5.1": version: 0.5.6 resolution: "mkdirp@npm:0.5.6" @@ -6349,52 +5169,6 @@ __metadata: languageName: node linkType: hard -"ml-array-mean@npm:^1.1.6": - version: 1.1.6 - resolution: "ml-array-mean@npm:1.1.6" - dependencies: - ml-array-sum: "npm:^1.1.6" - checksum: 10c0/41ab68308e3472702f775a49c8ab9ee1e678e01cd59dbc59424c0f1017a37df1bb638e702831305f0e6366300eca48353f526773ab8f4d8d142a64d0461f9944 - languageName: node - linkType: hard - -"ml-array-sum@npm:^1.1.6": - version: 1.1.6 - resolution: "ml-array-sum@npm:1.1.6" - dependencies: - is-any-array: "npm:^2.0.0" - checksum: 10c0/fb3973ce2bfa19ab4f5e657f722494425b57025547657d332bf5bafe3e4e7e4bd1e082dfb970bcc0bfa87efa345b80a20a596dbb204be7b4802b52c35fd6cf77 - languageName: node - linkType: hard - -"ml-distance-euclidean@npm:^2.0.0": - version: 2.0.0 - resolution: "ml-distance-euclidean@npm:2.0.0" - checksum: 10c0/877aef472e134f79be9540b02f889b2a27976ca45d77f5d4ef7d8dd24058a60cf4637365b40a5aba1ab5490348a0fb1b3803143b25af88cdc66137fbfd665442 - languageName: node - linkType: hard - -"ml-distance@npm:^4.0.0": - version: 4.0.1 - resolution: "ml-distance@npm:4.0.1" - dependencies: - ml-array-mean: "npm:^1.1.6" - ml-distance-euclidean: "npm:^2.0.0" - ml-tree-similarity: "npm:^1.0.0" - checksum: 10c0/8c2eb077d2ba61437f2414f3b9ca1091c43fcabe2282ecc31d8ebf9e083c1df068e43c67f59a4674e7c8f473201ace4f02779b446427d6169a5d669cae94c816 - languageName: node - linkType: hard - -"ml-tree-similarity@npm:^1.0.0": - version: 1.0.0 - resolution: "ml-tree-similarity@npm:1.0.0" - dependencies: - binary-search: "npm:^1.3.5" - num-sort: "npm:^2.0.0" - checksum: 10c0/e3ecd07bead5d18bc7b6fed1dfefbe65aea4008d5556181b94b7d70550fba543d2501b224f12a9f5197c1d23d95faef2accc7fd265c5afd15ef55a38190ffc6e - languageName: node - linkType: hard - "ms@npm:2.0.0": version: 2.0.0 resolution: "ms@npm:2.0.0" @@ -6402,22 +5176,13 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": +"ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 languageName: node linkType: hard -"mustache@npm:^4.2.0": - version: 4.2.0 - resolution: "mustache@npm:4.2.0" - bin: - mustache: bin/mustache - checksum: 10c0/1f8197e8a19e63645a786581d58c41df7853da26702dbc005193e2437c98ca49b255345c173d50c08fe4b4dbb363e53cb655ecc570791f8deb09887248dd34a2 - languageName: node - linkType: hard - "nan@npm:^2.12.1": version: 2.22.0 resolution: "nan@npm:2.22.0" @@ -6469,73 +5234,7 @@ __metadata: languageName: node linkType: hard -"node-abi@npm:^3.3.0": - version: 3.71.0 - resolution: "node-abi@npm:3.71.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/dbd0792ea729329cd9d099f28a5681ff9e8a6db48cf64e1437bf6a7fd669009d1e758a784619a1c4cc8bfd1ed17162f042c787654edf19a1f64b5018457c9c1f - languageName: node - linkType: hard - -"node-addon-api@npm:^6.0.0": - version: 6.1.0 - resolution: "node-addon-api@npm:6.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/d2699c4ad15740fd31482a3b6fca789af7723ab9d393adc6ac45250faaee72edad8f0b10b2b9d087df0de93f1bdc16d97afdd179b26b9ebc9ed68b569faa4bac - languageName: node - linkType: hard - -"node-addon-api@npm:^8.0.0": - version: 8.3.0 - resolution: "node-addon-api@npm:8.3.0" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/0ed4206cb68921b33fc637c6f7ffcb91fcde85aea88ea60fadb7b0537bf177226a5600584eac9db2aff93600041d42796fb20576b3299b9be6ff7539592b2180 - languageName: node - linkType: hard - -"node-domexception@npm:1.0.0": - version: 1.0.0 - resolution: "node-domexception@npm:1.0.0" - checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b - languageName: node - linkType: hard - -"node-ensure@npm:^0.0.0": - version: 0.0.0 - resolution: "node-ensure@npm:0.0.0" - checksum: 10c0/7af391aee024a8b7df77c239ed8b90417e3f2539824fa06b60f243ce14c75ee455766464c7c3ba9407d5b1e4d1d74ed5cf5f8af10c67b0fc05aa6e29f5d2462b - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.7": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: "npm:^5.0.0" - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 - languageName: node - linkType: hard - -"node-gyp-build@npm:^4.8.1": - version: 4.8.4 - resolution: "node-gyp-build@npm:4.8.4" - bin: - node-gyp-build: bin.js - node-gyp-build-optional: optional.js - node-gyp-build-test: build-test.js - checksum: 10c0/444e189907ece2081fe60e75368784f7782cfddb554b60123743dfb89509df89f1f29c03bbfa16b3a3e0be3f48799a4783f487da6203245fa5bed239ba7407e1 - languageName: node - linkType: hard - -"node-gyp@npm:^11.0.0, node-gyp@npm:latest": +"node-gyp@npm:latest": version: 11.0.0 resolution: "node-gyp@npm:11.0.0" dependencies: @@ -6618,15 +5317,6 @@ __metadata: languageName: node linkType: hard -"npm-run-path@npm:^3.1.0": - version: 3.1.0 - resolution: "npm-run-path@npm:3.1.0" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/8399f01239e9a5bf5a10bddbc71ecac97e0b7890e5b78abe9731fc759db48865b0686cc86ec079cd254a98ba119a3fa08f1b23f9de1a5428c19007bbc7b5a728 - languageName: node - linkType: hard - "npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -6636,13 +5326,6 @@ __metadata: languageName: node linkType: hard -"num-sort@npm:^2.0.0": - version: 2.1.0 - resolution: "num-sort@npm:2.1.0" - checksum: 10c0/cc1d43adbc9adfd5d208a8eb653827277376ff2e6eb75379f96e6a23d481040e317e63505e075b84ce49e19b9d960570646096428a715d12c5ef1381504d5135 - languageName: node - linkType: hard - "object-assign@npm:^4.0.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -6689,7 +5372,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -6707,35 +5390,6 @@ __metadata: languageName: node linkType: hard -"openai@npm:^4.41.1": - version: 4.77.0 - resolution: "openai@npm:4.77.0" - dependencies: - "@types/node": "npm:^18.11.18" - "@types/node-fetch": "npm:^2.6.4" - abort-controller: "npm:^3.0.0" - agentkeepalive: "npm:^4.2.1" - form-data-encoder: "npm:1.7.2" - formdata-node: "npm:^4.3.2" - node-fetch: "npm:^2.6.7" - peerDependencies: - zod: ^3.23.8 - peerDependenciesMeta: - zod: - optional: true - bin: - openai: bin/cli - checksum: 10c0/438e5acbcdc592ff192f294e936c10a8b71edf898b53afacb937da45f8d4e221e041bfcc84d6174c8dcb9ed4080b32760f8d94de1fcec7ab889046f1e1173f68 - languageName: node - linkType: hard - -"openapi-types@npm:^12.1.3": - version: 12.1.3 - resolution: "openapi-types@npm:12.1.3" - checksum: 10c0/4ad4eb91ea834c237edfa6ab31394e87e00c888fc2918009763389c00d02342345195d6f302d61c3fd807f17723cd48df29b47b538b68375b3827b3758cd520f - languageName: node - linkType: hard - "p-cancelable@npm:^0.3.0": version: 0.3.0 resolution: "p-cancelable@npm:0.3.0" @@ -6793,16 +5447,6 @@ __metadata: languageName: node linkType: hard -"p-queue@npm:^6.6.2": - version: 6.6.2 - resolution: "p-queue@npm:6.6.2" - dependencies: - eventemitter3: "npm:^4.0.4" - p-timeout: "npm:^3.2.0" - checksum: 10c0/5739ecf5806bbeadf8e463793d5e3004d08bb3f6177bd1a44a005da8fd81bb90f80e4633e1fb6f1dfd35ee663a5c0229abe26aebb36f547ad5a858347c7b0d3e - languageName: node - linkType: hard - "p-queue@npm:^8.0.1": version: 8.0.1 resolution: "p-queue@npm:8.0.1" @@ -6813,16 +5457,6 @@ __metadata: languageName: node linkType: hard -"p-retry@npm:4": - version: 4.6.2 - resolution: "p-retry@npm:4.6.2" - dependencies: - "@types/retry": "npm:0.12.0" - retry: "npm:^0.13.1" - checksum: 10c0/d58512f120f1590cfedb4c2e0c42cb3fa66f3cea8a4646632fcb834c56055bb7a6f138aa57b20cc236fb207c9d694e362e0b5c2b14d9b062f67e8925580c73b0 - languageName: node - linkType: hard - "p-timeout@npm:^1.1.1": version: 1.2.1 resolution: "p-timeout@npm:1.2.1" @@ -6832,15 +5466,6 @@ __metadata: languageName: node linkType: hard -"p-timeout@npm:^3.2.0": - version: 3.2.0 - resolution: "p-timeout@npm:3.2.0" - dependencies: - p-finally: "npm:^1.0.0" - checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 - languageName: node - linkType: hard - "p-timeout@npm:^6.1.2": version: 6.1.3 resolution: "p-timeout@npm:6.1.3" @@ -6974,16 +5599,6 @@ __metadata: languageName: node linkType: hard -"pdf-parse@npm:^1.1.1": - version: 1.1.1 - resolution: "pdf-parse@npm:1.1.1" - dependencies: - debug: "npm:^3.1.0" - node-ensure: "npm:^0.0.0" - checksum: 10c0/cba2b6ddfbfa73d94ff0cd342cbe8ef2ef0501863ada687eddf99487a4d06766e00fc44525c40cef3b01f04376cb99d5873ab789bd3e2379a28c3ae5377f3298 - languageName: node - linkType: hard - "pend@npm:~1.2.0": version: 1.2.0 resolution: "pend@npm:1.2.0" @@ -7069,22 +5684,6 @@ __metadata: languageName: node linkType: hard -"prebuildify@npm:^6.0.1": - version: 6.0.1 - resolution: "prebuildify@npm:6.0.1" - dependencies: - minimist: "npm:^1.2.5" - mkdirp-classic: "npm:^0.5.3" - node-abi: "npm:^3.3.0" - npm-run-path: "npm:^3.1.0" - pump: "npm:^3.0.0" - tar-fs: "npm:^2.1.0" - bin: - prebuildify: bin.js - checksum: 10c0/869a02fefe17ac5263194fa16db903640eeaaf2af68d52957016dbcfff6718cdf7909f3146bb420d39653f06d19edf9770a461226682304f743b9ddbb49c14a3 - languageName: node - linkType: hard - "prepend-http@npm:^1.0.1": version: 1.0.4 resolution: "prepend-http@npm:1.0.4" @@ -7151,16 +5750,6 @@ __metadata: languageName: node linkType: hard -"pump@npm:^3.0.0": - version: 3.0.2 - resolution: "pump@npm:3.0.2" - dependencies: - end-of-stream: "npm:^1.1.0" - once: "npm:^1.3.1" - checksum: 10c0/5ad655cb2a7738b4bcf6406b24ad0970d680649d996b55ad20d1be8e0c02394034e4c45ff7cd105d87f1e9b96a0e3d06fd28e11fae8875da26e7f7a8e2c9726f - languageName: node - linkType: hard - "pure-rand@npm:^6.0.0": version: 6.1.0 resolution: "pure-rand@npm:6.1.0" @@ -7222,17 +5811,6 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" - dependencies: - inherits: "npm:^2.0.3" - string_decoder: "npm:^1.1.1" - util-deprecate: "npm:^1.0.1" - checksum: 10c0/e37be5c79c376fdd088a45fa31ea2e423e5d48854be7a22a58869b4e84d25047b193f6acb54f1012331e1bcd667ffb569c01b99d36b0bd59658fb33f513511b7 - languageName: node - linkType: hard - "readdirp@npm:^2.0.0": version: 2.2.1 resolution: "readdirp@npm:2.2.1" @@ -7387,13 +5965,6 @@ __metadata: languageName: node linkType: hard -"retry@npm:^0.13.1": - version: 0.13.1 - resolution: "retry@npm:0.13.1" - checksum: 10c0/9ae822ee19db2163497e074ea919780b1efa00431d197c7afdb950e42bf109196774b92a49fc9821f0b8b328a98eea6017410bfc5e8a0fc19c85c6d11adb3772 - languageName: node - linkType: hard - "rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -7619,7 +6190,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 @@ -8029,15 +6600,6 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": - version: 1.3.0 - resolution: "string_decoder@npm:1.3.0" - dependencies: - safe-buffer: "npm:~5.2.0" - checksum: 10c0/810614ddb030e271cd591935dcd5956b2410dd079d64ff92a1844d6b7588bf992b3e1b69b0f4d34a3e06e0bd73046ac646b5264c1987b20d0601f81ef35d731d - languageName: node - linkType: hard - "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -8165,18 +6727,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^2.1.0": - version: 2.1.1 - resolution: "tar-fs@npm:2.1.1" - dependencies: - chownr: "npm:^1.1.1" - mkdirp-classic: "npm:^0.5.2" - pump: "npm:^3.0.0" - tar-stream: "npm:^2.1.4" - checksum: 10c0/871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d - languageName: node - linkType: hard - "tar-stream@npm:^1.5.2": version: 1.6.2 resolution: "tar-stream@npm:1.6.2" @@ -8192,19 +6742,6 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:^2.1.4": - version: 2.2.0 - resolution: "tar-stream@npm:2.2.0" - dependencies: - bl: "npm:^4.0.3" - end-of-stream: "npm:^1.4.1" - fs-constants: "npm:^1.0.0" - inherits: "npm:^2.0.3" - readable-stream: "npm:^3.1.1" - checksum: 10c0/2f4c910b3ee7196502e1ff015a7ba321ec6ea837667220d7bcb8d0852d51cb04b87f7ae471008a6fb8f5b1a1b5078f62f3a82d30c706f20ada1238ac797e7692 - languageName: node - linkType: hard - "tar@npm:^7.4.3": version: 7.4.3 resolution: "tar@npm:7.4.3" @@ -8333,13 +6870,6 @@ __metadata: languageName: node linkType: hard -"tr46@npm:~0.0.3": - version: 0.0.3 - resolution: "tr46@npm:0.0.3" - checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 - languageName: node - linkType: hard - "trim-newlines@npm:^1.0.0": version: 1.0.0 resolution: "trim-newlines@npm:1.0.0" @@ -8498,13 +7028,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 - languageName: node - linkType: hard - "undici-types@npm:~6.19.2": version: 6.19.8 resolution: "undici-types@npm:6.19.8" @@ -8603,31 +7126,13 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": +"util-deprecate@npm:~1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 languageName: node linkType: hard -"uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "uuid@npm:10.0.0" - bin: - uuid: dist/bin/uuid - checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe - languageName: node - linkType: hard - -"uuid@npm:^9.0.0": - version: 9.0.1 - resolution: "uuid@npm:9.0.1" - bin: - uuid: dist/bin/uuid - checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b - languageName: node - linkType: hard - "v8-to-istanbul@npm:^9.0.1": version: 9.3.0 resolution: "v8-to-istanbul@npm:9.3.0" @@ -8846,37 +7351,6 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:4.0.0-beta.3": - version: 4.0.0-beta.3 - resolution: "web-streams-polyfill@npm:4.0.0-beta.3" - checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e - languageName: node - linkType: hard - -"web-streams-polyfill@npm:^3.2.1": - version: 3.3.3 - resolution: "web-streams-polyfill@npm:3.3.3" - checksum: 10c0/64e855c47f6c8330b5436147db1c75cb7e7474d924166800e8e2aab5eb6c76aac4981a84261dd2982b3e754490900b99791c80ae1407a9fa0dcff74f82ea3a7f - languageName: node - linkType: hard - -"webidl-conversions@npm:^3.0.0": - version: 3.0.1 - resolution: "webidl-conversions@npm:3.0.1" - checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db - languageName: node - linkType: hard - -"whatwg-url@npm:^5.0.0": - version: 5.0.0 - resolution: "whatwg-url@npm:5.0.0" - dependencies: - tr46: "npm:~0.0.3" - webidl-conversions: "npm:^3.0.0" - checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 - languageName: node - linkType: hard - "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -8985,15 +7459,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.2.1": - version: 2.6.1 - resolution: "yaml@npm:2.6.1" - bin: - yaml: bin.mjs - checksum: 10c0/aebf07f61c72b38c74d2b60c3a3ccf89ee4da45bcd94b2bfb7899ba07a5257625a7c9f717c65a6fc511563d48001e01deb1d9e55f0133f3e2edf86039c8c1be7 - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" @@ -9033,25 +7498,7 @@ __metadata: languageName: node linkType: hard -"zod-to-json-schema@npm:3.20.3": - version: 3.20.3 - resolution: "zod-to-json-schema@npm:3.20.3" - peerDependencies: - zod: ^3.20.0 - checksum: 10c0/02e343df79936323482fd153c4e4ab68c0ced02fc2fdd8711cb6b21d158179e44202aa490c66bcd0bece99080fcd4ca5993d08a7bf2dcf4dcff2502239c5e6a0 - languageName: node - linkType: hard - -"zod-to-json-schema@npm:^3.22.3, zod-to-json-schema@npm:^3.22.5": - version: 3.24.1 - resolution: "zod-to-json-schema@npm:3.24.1" - peerDependencies: - zod: ^3.24.1 - checksum: 10c0/dd4e72085003e41a3f532bd00061f27041418a4eb176aa6ce33042db08d141bd37707017ee9117d97738ae3f22fc3e1404ea44e6354634ac5da79d7d3173b4ee - languageName: node - linkType: hard - -"zod@npm:^3.22.3, zod@npm:^3.22.4, zod@npm:^3.23.8": +"zod@npm:^3.23.8": version: 3.24.1 resolution: "zod@npm:3.24.1" checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b diff --git a/joi/.prettierignore b/joi/.prettierignore deleted file mode 100644 index e9e840d7e..000000000 --- a/joi/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -.next/ -node_modules/ -dist/ -*.hbs -*.mdx -*.mjs \ No newline at end of file diff --git a/joi/.prettierrc b/joi/.prettierrc deleted file mode 100644 index 933d88d62..000000000 --- a/joi/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "semi": false, - "singleQuote": true, - "quoteProps": "consistent", - "trailingComma": "es5", - "endOfLine": "lf", - "plugins": ["prettier-plugin-tailwindcss"] -} diff --git a/joi/README.md b/joi/README.md deleted file mode 100644 index 161db4156..000000000 --- a/joi/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @janhq/joi - -To install dependencies: - -```bash -yarn install -``` - -To run: - -```bash -yarn run dev -``` diff --git a/joi/jest.config.js b/joi/jest.config.js deleted file mode 100644 index 676042491..000000000 --- a/joi/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: ['/src'], - testMatch: ['**/*.test.*'], - collectCoverageFrom: ['src/**/*.{ts,tsx}'], - setupFilesAfterEnv: ['/jest.setup.js'], - testEnvironment: 'jsdom', -} diff --git a/joi/jest.setup.js b/joi/jest.setup.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/joi/package.json b/joi/package.json deleted file mode 100644 index 2f7d771d5..000000000 --- a/joi/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "@janhq/joi", - "version": "0.0.0", - "main": "dist/esm/index.js", - "types": "dist/index.d.ts", - "description": "A collection of UI component", - "files": [ - "dist" - ], - "keywords": [ - "design-system" - ], - "license": "MIT", - "homepage": "https://github.com/codecentrum/piksel#readme", - "repository": { - "type": "git", - "url": "https://github.com/codecentrum/piksel.git" - }, - "bugs": "https://github.com/codecentrum/piksel/issues", - "scripts": { - "dev": "rollup -c -w", - "build": "rimraf ./dist || true && rollup -c", - "test": "jest" - }, - "peerDependencies": { - "class-variance-authority": "^0.7.0", - "react": "^18", - "typescript": "^5.0.0" - }, - "dependencies": { - "@radix-ui/react-accordion": "^1.1.2", - "@radix-ui/react-dialog": "^1.0.5", - "@radix-ui/react-dropdown-menu": "^2.1.4", - "@radix-ui/react-icons": "^1.3.0", - "@radix-ui/react-scroll-area": "^1.0.5", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-slider": "^1.1.2", - "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-tabs": "^1.0.4", - "@radix-ui/react-tooltip": "^1.0.7", - "@types/jest": "^29.5.12", - "autoprefixer": "10.4.16", - "jest": "^29.7.0", - "tailwind-merge": "^2.2.0", - "tailwindcss": "^3.4.1", - "ts-jest": "^29.2.5" - }, - "devDependencies": { - "@rollup/plugin-node-resolve": "15.2.3", - "@rollup/plugin-terser": "0.4.4", - "@testing-library/dom": "10.4.0", - "@testing-library/jest-dom": "^6.5.0", - "@testing-library/react": "^16.0.1", - "@testing-library/user-event": "^14.5.2", - "@types/jest": "^29.5.12", - "@types/react": "^18.3.12", - "@types/react-dom": "^19", - "class-variance-authority": "^0.7.0", - "jest-environment-jsdom": "^29.7.0", - "jest-transform-css": "^6.0.1", - "prettier": "^3.0.3", - "prettier-plugin-tailwindcss": "^0.5.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "rimraf": "^6.0.1", - "rollup": "4.12.0", - "rollup-plugin-bundle-size": "1.0.3", - "rollup-plugin-commonjs": "10.1.0", - "rollup-plugin-copy": "3.5.0", - "rollup-plugin-dts": "6.1.0", - "rollup-plugin-peer-deps-external": "2.2.4", - "rollup-plugin-postcss": "4.0.2", - "rollup-plugin-typescript2": "0.36.0", - "sass": "^1.83.1", - "typescript": "^5.7.2" - }, - "packageManager": "yarn@4.5.3" -} diff --git a/joi/rollup.config.mjs b/joi/rollup.config.mjs deleted file mode 100644 index 6577135dc..000000000 --- a/joi/rollup.config.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import { readFileSync } from 'fs' -import dts from 'rollup-plugin-dts' -import terser from '@rollup/plugin-terser' -import autoprefixer from 'autoprefixer' -import commonjs from 'rollup-plugin-commonjs' -import bundleSize from 'rollup-plugin-bundle-size' -import peerDepsExternal from 'rollup-plugin-peer-deps-external' -import postcss from 'rollup-plugin-postcss' -import typescript from 'rollup-plugin-typescript2' -import tailwindcss from 'tailwindcss' -import typescriptEngine from 'typescript' -import resolve from '@rollup/plugin-node-resolve' -import copy from 'rollup-plugin-copy' - -const packageJson = JSON.parse(readFileSync('./package.json')) - -import tailwindConfig from './tailwind.config.js' - -export default [ - { - input: `./src/index.ts`, - output: [ - { - file: packageJson.main, - format: 'es', - exports: 'named', - sourcemap: false, - }, - ], - external: ['react', 'typescript', 'class-variance-authority'], - plugins: [ - postcss({ - plugins: [autoprefixer(), tailwindcss(tailwindConfig)], - sourceMap: true, - use: { - sass: { - silenceDeprecations: ['legacy-js-api'], - api: 'modern', - }, - }, - minimize: true, - extract: 'main.css', - }), - - peerDepsExternal({ includeDependencies: true }), - commonjs(), - resolve(), - typescript({ - tsconfig: './tsconfig.json', - typescript: typescriptEngine, - sourceMap: false, - exclude: [ - 'docs', - 'dist', - 'node_modules/**', - '**/*.test.ts', - '**/*.test.tsx', - ], - }), - terser(), - ], - watch: { - clearScreen: false, - }, - }, - { - input: 'dist/esm/index.d.ts', - output: [{ file: 'dist/index.d.ts', format: 'esm' }], - external: [/\.(sc|sa|c)ss$/], - plugins: [ - dts(), - peerDepsExternal({ includeDependencies: true }), - copy({ - targets: [{ src: 'dist/esm/main.css', dest: 'dist' }], - }), - bundleSize(), - ], - }, -] diff --git a/joi/src/core/Accordion/Accordion.test.tsx b/joi/src/core/Accordion/Accordion.test.tsx deleted file mode 100644 index 62b575ea3..000000000 --- a/joi/src/core/Accordion/Accordion.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' -import '@testing-library/jest-dom' -import { render, screen, fireEvent } from '@testing-library/react' -import { Accordion, AccordionItem } from './index' - -// Mock the SCSS import -jest.mock('./styles.scss', () => ({})) - -describe('Accordion', () => { - it('renders accordion with items', () => { - render( - - - Content 1 - - - Content 2 - - - ) - - expect(screen.getByText('Item 1')).toBeInTheDocument() - expect(screen.getByText('Item 2')).toBeInTheDocument() - }) - - it('expands and collapses accordion items', () => { - render( - - - Content 1 - - - ) - - const trigger = screen.getByText('Item 1') - - // Initially, content should not be visible - expect(screen.queryByText('Content 1')).not.toBeInTheDocument() - - // Click to expand - fireEvent.click(trigger) - expect(screen.getByText('Content 1')).toBeInTheDocument() - - // Click to collapse - fireEvent.click(trigger) - expect(screen.queryByText('Content 1')).not.toBeInTheDocument() - }) - - it('respects defaultValue prop', () => { - render( - - - Content 1 - - - Content 2 - - - ) - - expect(screen.queryByText('Content 1')).not.toBeInTheDocument() - expect(screen.getByText('Content 2')).toBeInTheDocument() - }) -}) diff --git a/joi/src/core/Accordion/index.tsx b/joi/src/core/Accordion/index.tsx deleted file mode 100644 index 75a671ca4..000000000 --- a/joi/src/core/Accordion/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { ReactNode } from 'react' -import * as AccordionPrimitive from '@radix-ui/react-accordion' - -import { ChevronDownIcon } from '@radix-ui/react-icons' - -import './styles.scss' - -type AccordionProps = { - defaultValue: string[] - children: ReactNode -} - -type AccordionItemProps = { - children: ReactNode - value: string - title: string -} - -const AccordionItem = ({ children, value, title }: AccordionItemProps) => { - return ( - - - -
{title}
- -
-
- -
{children}
-
-
- ) -} - -const Accordion = ({ defaultValue, children }: AccordionProps) => ( - - {children} - -) - -export { Accordion, AccordionItem } diff --git a/joi/src/core/Accordion/styles.scss b/joi/src/core/Accordion/styles.scss deleted file mode 100644 index 028cc021c..000000000 --- a/joi/src/core/Accordion/styles.scss +++ /dev/null @@ -1,73 +0,0 @@ -.accordion { - border-top: 1px solid hsla(var(--app-border)); - - &__item { - overflow: hidden; - margin-top: 1px; - border-bottom: 1px solid hsla(var(--app-border)); - - :focus-within { - position: relative; - z-index: 1; - } - } - - &__header { - display: flex; - } - - &__trigger { - font-family: inherit; - background-color: transparent; - padding: 0 16px; - height: 40px; - flex: 1; - display: flex; - align-items: center; - justify-content: space-between; - font-weight: 500; - } - - &__content { - overflow: hidden; - - &--wrapper { - padding: 4px 16px 16px 16px; - } - } - - &__chevron { - color: hsla(var(--text-secondary)); - transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1); - } -} - -.accordion__content[data-state='open'] { - animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); -} - -.accordion__content[data-state='closed'] { - animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); -} - -.accordion__trigger[data-state='open'] > .accordion__chevron { - transform: rotate(180deg); -} - -@keyframes slideDown { - from { - height: 0; - } - to { - height: var(--radix-accordion-content-height); - } -} - -@keyframes slideUp { - from { - height: var(--radix-accordion-content-height); - } - to { - height: 0; - } -} diff --git a/joi/src/core/Badge/Badge.test.tsx b/joi/src/core/Badge/Badge.test.tsx deleted file mode 100644 index 1d3192be7..000000000 --- a/joi/src/core/Badge/Badge.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Badge, badgeConfig } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Badge', () => { - it('renders with default props', () => { - render(Test Badge) - const badge = screen.getByText('Test Badge') - expect(badge).toBeInTheDocument() - expect(badge).toHaveClass('badge') - expect(badge).toHaveClass('badge--primary') - expect(badge).toHaveClass('badge--medium') - expect(badge).toHaveClass('badge--solid') - }) - - it('applies custom className', () => { - render(Test Badge) - const badge = screen.getByText('Test Badge') - expect(badge).toHaveClass('custom-class') - }) - - it('renders with different themes', () => { - const themes = Object.keys(badgeConfig.variants.theme) - themes.forEach((theme) => { - render(Test Badge {theme}) - const badge = screen.getByText(`Test Badge ${theme}`) - expect(badge).toHaveClass(`badge--${theme}`) - }) - }) - - it('renders with different variants', () => { - const variants = Object.keys(badgeConfig.variants.variant) - variants.forEach((variant) => { - render(Test Badge {variant}) - const badge = screen.getByText(`Test Badge ${variant}`) - expect(badge).toHaveClass(`badge--${variant}`) - }) - }) - - it('renders with different sizes', () => { - const sizes = Object.keys(badgeConfig.variants.size) - sizes.forEach((size) => { - render(Test Badge {size}) - const badge = screen.getByText(`Test Badge ${size}`) - expect(badge).toHaveClass(`badge--${size}`) - }) - }) - - it('fails when a new theme is added without updating the test', () => { - const expectedThemes = [ - 'primary', - 'secondary', - 'warning', - 'success', - 'info', - 'destructive', - ] - const actualThemes = Object.keys(badgeConfig.variants.theme) - expect(actualThemes).toEqual(expectedThemes) - }) - - it('fails when a new variant is added without updating the test', () => { - const expectedVariant = ['solid', 'soft', 'outline'] - const actualVariants = Object.keys(badgeConfig.variants.variant) - expect(actualVariants).toEqual(expectedVariant) - }) - - it('fails when a new size is added without updating the test', () => { - const expectedSizes = ['small', 'medium', 'large'] - const actualSizes = Object.keys(badgeConfig.variants.size) - expect(actualSizes).toEqual(expectedSizes) - }) - - it('fails when a new variant CVA is added without updating the test', () => { - const expectedVariantsCVA = ['theme', 'variant', 'size'] - const actualVariant = Object.keys(badgeConfig.variants) - expect(actualVariant).toEqual(expectedVariantsCVA) - }) -}) diff --git a/joi/src/core/Badge/index.tsx b/joi/src/core/Badge/index.tsx deleted file mode 100644 index 5aeb19631..000000000 --- a/joi/src/core/Badge/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { HTMLAttributes } from 'react' - -import { cva, type VariantProps } from 'class-variance-authority' - -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -export const badgeConfig = { - variants: { - theme: { - primary: 'badge--primary', - secondary: 'badge--secondary', - warning: 'badge--warning', - success: 'badge--success', - info: 'badge--info', - destructive: 'badge--destructive', - }, - variant: { - solid: 'badge--solid', - soft: 'badge--soft', - outline: 'badge--outline', - }, - size: { - small: 'badge--small', - medium: 'badge--medium', - large: 'badge--large', - }, - }, - defaultVariants: { - theme: 'primary' as const, - size: 'medium' as const, - variant: 'solid' as const, - }, -} - -const badgeVariants = cva('badge', badgeConfig) - -export interface BadgeProps - extends HTMLAttributes, - VariantProps {} - -const Badge = ({ className, theme, size, variant, ...props }: BadgeProps) => { - return ( -
- ) -} - -export { Badge, badgeVariants } diff --git a/joi/src/core/Badge/styles.scss b/joi/src/core/Badge/styles.scss deleted file mode 100644 index a912e9216..000000000 --- a/joi/src/core/Badge/styles.scss +++ /dev/null @@ -1,131 +0,0 @@ -.badge { - @apply inline-flex items-center justify-center px-2 font-medium transition-all; - - // Primary - &--primary { - color: hsla(var(--primary-fg)); - background-color: hsla(var(--primary-bg)); - - // Variant soft primary - &.badge--soft { - background-color: hsla(var(--primary-bg-soft)); - color: hsla(var(--primary-bg)); - } - - // Variant outline primary - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--primary-bg)); - color: hsla(var(--primary-bg)); - } - } - - // Secondary - &--secondary { - background-color: hsla(var(--secondary-bg)); - color: hsla(var(--secondary-fg)); - - &.badge--soft { - background-color: hsla(var(--secondary-bg-soft)); - color: hsla(var(--secondary-bg)); - } - - // Variant outline secondary - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--secondary-bg)); - } - } - - // Destructive - &--destructive { - color: hsla(var(--destructive-fg)); - background-color: hsla(var(--destructive-bg)); - - // Variant soft destructive - &.badge--soft { - background-color: hsla(var(--destructive-bg-soft)); - color: hsla(var(--destructive-bg)); - } - - // Variant outline destructive - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--destructive-bg)); - color: hsla(var(--destructive-bg)); - } - } - - // Success - &--success { - @apply text-white; - background-color: hsla(var(--success-bg)); - - // Variant soft success - &.badge--soft { - background-color: hsla(var(--success-bg-soft)); - color: hsla(var(--success-bg)); - } - - // Variant outline success - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--success-bg)); - color: hsla(var(--success-bg)); - } - } - - // Warning - &--warning { - @apply text-white; - background-color: hsla(var(--warning-bg)); - - // Variant soft warning - &.badge--soft { - background-color: hsla(var(--warning-bg-soft)); - color: hsla(var(--warning-bg)); - } - - // Variant outline warning - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--warning-bg)); - color: hsla(var(--warning-bg)); - } - } - - // Info - &--info { - @apply text-white; - background-color: hsla(var(--info-bg)); - - // Variant soft info - &.badge--soft { - background-color: hsla(var(--info-bg-soft)); - color: hsla(var(--info-bg)); - } - - // Variant outline info - &.badge--outline { - background-color: transparent; - border: 1px solid hsla(var(--info-bg)); - color: hsla(var(--info-bg)); - } - } - - // Size - &--small { - @apply h-5; - border-radius: 4px; - } - - &--medium { - @apply h-6; - border-radius: 6px; - } - - &--large { - @apply h-7; - border-radius: 8px; - } -} diff --git a/joi/src/core/Button/Button.test.tsx b/joi/src/core/Button/Button.test.tsx deleted file mode 100644 index a4c679773..000000000 --- a/joi/src/core/Button/Button.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Button, buttonConfig } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Button', () => { - it('renders with default props', () => { - render() - const button = screen.getByRole('button', { name: /click me/i }) - expect(button).toBeInTheDocument() - expect(button).toHaveClass('btn btn--primary btn--medium btn--solid') - }) - - it('applies custom className', () => { - render() - const badge = screen.getByText('Test Button') - expect(badge).toHaveClass('custom-class') - }) - - it('renders as a child component when asChild is true', () => { - render( - - ) - const link = screen.getByRole('link', { name: /link button/i }) - expect(link).toBeInTheDocument() - expect(link).toHaveClass('btn btn--primary btn--medium btn--solid') - }) - - it.each(Object.keys(buttonConfig.variants.theme))( - 'renders with theme %s', - (theme) => { - render() - const button = screen.getByRole('button', { name: /theme button/i }) - expect(button).toHaveClass(`btn btn--${theme}`) - } - ) - - it.each(Object.keys(buttonConfig.variants.variant))( - 'renders with variant %s', - (variant) => { - render() - const button = screen.getByRole('button', { name: /variant button/i }) - expect(button).toHaveClass(`btn btn--${variant}`) - } - ) - - it.each(Object.keys(buttonConfig.variants.size))( - 'renders with size %s', - (size) => { - render() - const button = screen.getByRole('button', { name: /size button/i }) - expect(button).toHaveClass(`btn btn--${size}`) - } - ) - - it('renders with block prop', () => { - render() - const button = screen.getByRole('button', { name: /block button/i }) - expect(button).toHaveClass('btn btn--block') - }) - - it('fails when a new theme is added without updating the test', () => { - const expectedThemes = ['primary', 'ghost', 'icon', 'destructive'] - const actualThemes = Object.keys(buttonConfig.variants.theme) - expect(actualThemes).toEqual(expectedThemes) - }) - - it('fails when a new variant is added without updating the test', () => { - const expectedVariant = ['solid', 'soft', 'outline'] - const actualVariants = Object.keys(buttonConfig.variants.variant) - expect(actualVariants).toEqual(expectedVariant) - }) - - it('fails when a new size is added without updating the test', () => { - const expectedSizes = ['small', 'medium', 'large'] - const actualSizes = Object.keys(buttonConfig.variants.size) - expect(actualSizes).toEqual(expectedSizes) - }) - - it('fails when a new variant CVA is added without updating the test', () => { - const expectedVariantsCVA = ['theme', 'variant', 'size', 'block'] - const actualVariant = Object.keys(buttonConfig.variants) - expect(actualVariant).toEqual(expectedVariantsCVA) - }) -}) diff --git a/joi/src/core/Button/index.tsx b/joi/src/core/Button/index.tsx deleted file mode 100644 index 9945eb4e9..000000000 --- a/joi/src/core/Button/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { forwardRef, ButtonHTMLAttributes } from 'react' - -import { Slot } from '@radix-ui/react-slot' -import { cva, type VariantProps } from 'class-variance-authority' - -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -export const buttonConfig = { - variants: { - theme: { - primary: 'btn--primary', - ghost: 'btn--ghost', - icon: 'btn--icon', - destructive: 'btn--destructive', - }, - variant: { - solid: 'btn--solid', - soft: 'btn--soft', - outline: 'btn--outline', - }, - size: { - small: 'btn--small', - medium: 'btn--medium', - large: 'btn--large', - }, - block: { - true: 'btn--block', - }, - }, - defaultVariants: { - theme: 'primary' as const, - size: 'medium' as const, - variant: 'solid' as const, - block: false as const, - }, -} -const buttonVariants = cva('btn', buttonConfig) - -export interface ButtonProps - extends ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} - -const Button = forwardRef( - ( - { className, theme, size, variant, block, asChild = false, ...props }, - ref - ) => { - const Comp = asChild ? Slot : 'button' - return ( - - ) - } -) - -export { Button } diff --git a/joi/src/core/Button/styles.scss b/joi/src/core/Button/styles.scss deleted file mode 100644 index f7cdce6a4..000000000 --- a/joi/src/core/Button/styles.scss +++ /dev/null @@ -1,134 +0,0 @@ -.btn { - @apply inline-flex items-center justify-center px-4 font-semibold transition-all; - - &:focus, - &:focus-within { - @apply outline-2 outline-offset-4; - } - &:hover { - filter: brightness(95%); - } - - // Primary - &--primary { - color: hsla(var(--primary-fg)); - background-color: hsla(var(--primary-bg)) !important; - &:hover { - filter: brightness(65%); - } - - // Variant soft primary - &.btn--soft { - background-color: hsla(var(--primary-bg-soft)) !important; - color: hsla(var(--primary-bg)); - } - - // Variant outline primary - &.btn--outline { - background-color: transparent !important; - border: 1px solid hsla(var(--primary-bg)); - color: hsla(var(--primary-bg)); - } - } - - // Ghost - &--ghost { - background-color: transparent !important; - &.btn--soft { - background-color: transparent !important; - } - - // Variant outline ghost - &.btn--outline { - background-color: transparent !important; - border: 1px solid hsla(var(--ghost-border)); - } - } - - // Destructive - &--destructive { - color: hsla(var(--destructive-fg)); - background-color: hsla(var(--destructive-bg)) !important; - &:hover { - filter: brightness(65%); - } - - // Variant soft destructive - &.btn--soft { - background-color: hsla(var(--destructive-bg-soft)) !important; - color: hsla(var(--destructive-bg)); - } - - // Variant outline destructive - &.btn--outline { - background-color: transparent !important; - border: 1px solid hsla(var(--destructive-bg)); - color: hsla(var(--destructive-bg)); - } - } - - // Disabled - &:disabled { - color: hsla(var(--disabled-fg)); - background-color: hsla(var(--disabled-bg)) !important; - cursor: not-allowed; - - &:hover { - filter: brightness(100%); - } - } - - // Icon - &--icon { - width: 24px; - height: 24px; - padding: 2px; - &:hover { - background-color: hsla(var(--icon-bg)) !important; - } - - &.btn--outline { - background-color: transparent !important; - border: 1px solid hsla(var(--icon-border)); - &:hover { - background-color: hsla(var(--icon-bg)) !important; - } - } - } - - // Size - &--small { - @apply h-6 px-2; - font-size: 12px; - border-radius: 4px; - &.btn--icon { - width: 24px; - height: 24px; - padding: 2px; - } - } - - &--medium { - @apply h-8; - border-radius: 6px; - &.btn--icon { - width: 24px; - height: 24px; - padding: 2px; - } - } - - &--large { - @apply h-9; - border-radius: 8px; - &.btn--icon { - width: 24px; - height: 24px; - padding: 2px; - } - } - - &--block { - @apply w-full; - } -} diff --git a/joi/src/core/Checkbox/Checkbox.test.tsx b/joi/src/core/Checkbox/Checkbox.test.tsx deleted file mode 100644 index ce81132d9..000000000 --- a/joi/src/core/Checkbox/Checkbox.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Checkbox } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Checkbox', () => { - it('renders correctly with label', () => { - render() - expect(screen.getByLabelText('Test Checkbox')).toBeInTheDocument() - }) - - it('renders with helper description', () => { - render() - expect(screen.getByText('Helper text')).toBeInTheDocument() - }) - - it('renders error message when provided', () => { - render() - expect(screen.getByText('Error occurred')).toBeInTheDocument() - }) - - it('calls onChange when clicked', () => { - const mockOnChange = jest.fn() - render( - - ) - - fireEvent.click(screen.getByLabelText('Test Checkbox')) - expect(mockOnChange).toHaveBeenCalledTimes(1) - }) - - it('applies custom className', () => { - render() - expect(screen.getByRole('checkbox').parentElement).toHaveClass( - 'custom-class' - ) - }) - - it('disables the checkbox when disabled prop is true', () => { - render() - expect(screen.getByLabelText('Disabled Checkbox')).toBeDisabled() - }) -}) diff --git a/joi/src/core/Checkbox/index.tsx b/joi/src/core/Checkbox/index.tsx deleted file mode 100644 index 71f9523ac..000000000 --- a/joi/src/core/Checkbox/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { ChangeEvent, InputHTMLAttributes, ReactNode } from 'react' - -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -export interface CheckboxProps extends InputHTMLAttributes { - disabled?: boolean - className?: string - label?: ReactNode - helperDescription?: ReactNode - errorMessage?: string - onChange?: (e: ChangeEvent) => void -} - -const Checkbox = ({ - id, - name, - checked, - disabled, - label, - defaultChecked, - helperDescription, - errorMessage, - className, - onChange, - ...props -}: CheckboxProps) => { - return ( -
- -
- -

{helperDescription}

- {errorMessage &&

{errorMessage}

} -
-
- ) -} -export { Checkbox } diff --git a/joi/src/core/Checkbox/styles.scss b/joi/src/core/Checkbox/styles.scss deleted file mode 100644 index 775a6289b..000000000 --- a/joi/src/core/Checkbox/styles.scss +++ /dev/null @@ -1,51 +0,0 @@ -.checkbox { - @apply inline-flex items-start space-x-2; - - > input[type='checkbox'] { - @apply flex h-4 w-4 flex-shrink-0 cursor-pointer appearance-none items-center justify-center; - background-color: transparent; - margin-top: 1px; - border: 1px solid hsla(var(--app-border)); - border-radius: 4px; - &:focus, - &:focus-within { - @apply outline-2 outline-offset-4; - } - - &:checked { - background-color: hsla(var(--primary-bg)); - background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E"); - } - - &:disabled { - background-color: hsla(var(----disabled-bg)); - color: hsla(var(--disabled-fg)); - - &:checked { - background-color: hsla(var(--primary-bg)); - @apply cursor-not-allowed opacity-50; - } - - & + div > .checkbox__label { - @apply cursor-not-allowed opacity-50; - } - } - } - - &__helper { - font-size: 12px; - } - - &__error { - color: hsla(var(--destructive-bg)); - } - - &__label { - @apply inline-block cursor-pointer; - } - - &:disabled { - background-color: hsla(var(----disabled-bg)); - color: hsla(var(--disabled-fg)); - } -} diff --git a/joi/src/core/Dropdown/index.tsx b/joi/src/core/Dropdown/index.tsx deleted file mode 100644 index 6d9abcbea..000000000 --- a/joi/src/core/Dropdown/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { Fragment, PropsWithChildren, ReactNode } from 'react' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import './styles.scss' -import { twMerge } from 'tailwind-merge' - -type Props = { - options?: { name: ReactNode; value: string; suffix?: ReactNode }[] - className?: string - onValueChanged?: (value: string) => void -} - -const Dropdown = (props: PropsWithChildren & Props) => { - return ( - - {props.children} - - - - {props.options?.map((e, i) => ( - - {i !== 0 && ( - - )} - props.onValueChanged?.(e.value)} - > - {e.name} -
- {e.suffix} - - - ))} - - - - - ) -} - -export { Dropdown } diff --git a/joi/src/core/Dropdown/styles.scss b/joi/src/core/Dropdown/styles.scss deleted file mode 100644 index d472578df..000000000 --- a/joi/src/core/Dropdown/styles.scss +++ /dev/null @@ -1,155 +0,0 @@ -.DropdownMenuContent, -.DropdownMenuSubContent { - min-width: 220px; - background-color: white; - border-radius: 6px; - overflow: hidden; - padding: 0px; - box-shadow: - 0px 10px 38px -10px rgba(22, 23, 24, 0.35), - 0px 10px 20px -15px rgba(22, 23, 24, 0.2); - animation-duration: 400ms; - animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1); - will-change: transform, opacity; -} -.DropdownMenuContent[data-side='top'], -.DropdownMenuSubContent[data-side='top'] { - animation-name: slideDownAndFade; -} -.DropdownMenuContent[data-side='right'], -.DropdownMenuSubContent[data-side='right'] { - animation-name: slideLeftAndFade; -} -.DropdownMenuContent[data-side='bottom'], -.DropdownMenuSubContent[data-side='bottom'] { - animation-name: slideUpAndFade; -} -.DropdownMenuContent[data-side='left'], -.DropdownMenuSubContent[data-side='left'] { - animation-name: slideRightAndFade; -} - -.DropdownMenuItem { - padding: 14px; - cursor: pointer; - outline: none; - flex: 1; - display: flex; - justify-content: space-between; /* Distribute space between children */ - align-items: center; /* Optional: Align items vertically */ - gap: 16px; - border-color: hsla(var(--app-border)); -} -.DropdownMenuCheckboxItem, -.DropdownMenuRadioItem, -.DropdownMenuSubTrigger { - font-size: 13px; - line-height: 1; - border-radius: 3px; - display: flex; - align-items: center; - height: 25px; - padding: 0 0; - position: relative; - padding-left: 25px; - user-select: none; - outline: none; -} -.DropdownMenuItem[data-disabled], -.DropdownMenuCheckboxItem[data-disabled], -.DropdownMenuRadioItem[data-disabled], -.DropdownMenuSubTrigger[data-disabled] { - pointer-events: none; -} -.DropdownMenuItem[data-highlighted], -.DropdownMenuCheckboxItem[data-highlighted], -.DropdownMenuRadioItem[data-highlighted], -.DropdownMenuSubTrigger[data-highlighted] { - background-color: hsla(var(--secondary-bg)); -} - -.DropdownMenuSeparator { - height: 1px; - width: '100%'; - background-color: hsla(var(--app-border)); -} - -.DropdownMenuItem::hover { - background-color: hsla(var(--secondary-bg)); -} - -.DropdownMenuLabel { - padding-left: 25px; - font-size: 12px; - line-height: 25px; - color: var(--mauve-11); -} - -.DropdownMenuItemIndicator { - position: absolute; - left: 0; - width: 25px; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.DropdownMenuArrow { - fill: white; -} - -.RightSlot { - margin-left: auto; - padding-left: 20px; - color: var(--mauve-11); -} -[data-highlighted] > .RightSlot { - color: white; -} -[data-disabled] .RightSlot { - color: var(--mauve-8); -} - -@keyframes slideUpAndFade { - from { - opacity: 0; - transform: translateY(2px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideRightAndFade { - from { - opacity: 0; - transform: translateX(-2px); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -@keyframes slideDownAndFade { - from { - opacity: 0; - transform: translateY(-2px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideLeftAndFade { - from { - opacity: 0; - transform: translateX(2px); - } - to { - opacity: 1; - transform: translateX(0); - } -} diff --git a/joi/src/core/Input/Input.test.tsx b/joi/src/core/Input/Input.test.tsx deleted file mode 100644 index 55bed74bb..000000000 --- a/joi/src/core/Input/Input.test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Input } from './index' - -// Mock the styles import -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Input', () => { - it('renders correctly', () => { - render() - expect(screen.getByPlaceholderText('Test input')).toBeInTheDocument() - }) - - it('applies custom className', () => { - render() - expect(screen.getByRole('textbox')).toHaveClass('custom-class') - }) - - it('aligns text to the right when textAlign prop is set', () => { - render() - expect(screen.getByRole('textbox')).toHaveClass('text-right') - }) - - it('renders prefix icon when provided', () => { - render(Prefix} />) - expect(screen.getByTestId('prefix-icon')).toBeInTheDocument() - }) - - it('renders suffix icon when provided', () => { - render(Suffix} />) - expect(screen.getByTestId('suffix-icon')).toBeInTheDocument() - }) - - it('renders clear icon when clearable is true', () => { - render() - expect(screen.getByTestId('cross-2-icon')).toBeInTheDocument() - }) - - it('calls onClick when input is clicked', () => { - const onClick = jest.fn() - render() - fireEvent.click(screen.getByRole('textbox')) - expect(onClick).toHaveBeenCalledTimes(1) - }) - - it('calls onClear when clear icon is clicked', () => { - const onClear = jest.fn() - render() - fireEvent.click(screen.getByTestId('cross-2-icon')) - expect(onClear).toHaveBeenCalledTimes(1) - }) -}) diff --git a/joi/src/core/Input/index.tsx b/joi/src/core/Input/index.tsx deleted file mode 100644 index 9f5e4c663..000000000 --- a/joi/src/core/Input/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { ReactNode, forwardRef } from 'react' -import { twMerge } from 'tailwind-merge' - -import './styles.scss' -import { Cross2Icon } from '@radix-ui/react-icons' - -export interface Props extends React.InputHTMLAttributes { - textAlign?: 'left' | 'right' - prefixIcon?: ReactNode - suffixIcon?: ReactNode - onCLick?: () => void - clearable?: boolean - onClear?: () => void -} - -const Input = forwardRef( - ( - { - className, - type, - textAlign, - prefixIcon, - suffixIcon, - onClick, - onClear, - clearable, - ...props - }, - ref - ) => { - return ( -
- {prefixIcon && ( -
- {prefixIcon} -
- )} - {suffixIcon && ( -
- {suffixIcon} -
- )} - {clearable && ( -
- -
- )} - -
- ) - } -) - -export { Input } diff --git a/joi/src/core/Input/styles.scss b/joi/src/core/Input/styles.scss deleted file mode 100644 index 540d880b1..000000000 --- a/joi/src/core/Input/styles.scss +++ /dev/null @@ -1,50 +0,0 @@ -.input { - background-color: hsla(var(--input-bg)); - border: 1px solid hsla(var(--app-border)); - @apply inline-flex h-8 w-full items-center rounded-md border px-3 transition-colors; - @apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0; - @apply file:border-0 file:bg-transparent file:font-medium; - @apply hover:border-[hsla(var(--primary-bg))]; - - &__wrapper { - position: relative; - } - - &.text-right { - text-align: right; - } - - &::placeholder { - color: hsla(var(--input-placeholder)); - } - - &:disabled { - color: hsla(var(--disabled-fg)); - background-color: hsla(var(--disabled-bg)); - cursor: not-allowed; - border: none; - } - - &__prefix-icon { - @apply absolute left-3 top-1/2 -translate-y-1/2 cursor-pointer; - color: hsla(var(--input-icon)); - + .input { - padding-left: 32px; - } - } - - &__suffix-icon { - @apply absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer; - color: hsla(var(--input-icon)); - + .input { - padding-right: 32px; - } - } - &__clear-icon { - @apply absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer; - color: hsla(var(--input-icon)); - + .input { - padding: 0 32px; - } - } -} diff --git a/joi/src/core/Modal/Modal.test.tsx b/joi/src/core/Modal/Modal.test.tsx deleted file mode 100644 index fe7ca7eac..000000000 --- a/joi/src/core/Modal/Modal.test.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Modal } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('Modal', () => { - it('renders the modal with trigger and content', () => { - render( - Open Modal} - content={
Modal Content
} - /> - ) - - expect(screen.getByText('Open Modal')).toBeInTheDocument() - fireEvent.click(screen.getByText('Open Modal')) - expect(screen.getByText('Modal Content')).toBeInTheDocument() - }) - - it('renders the modal with title', () => { - render( - Open Modal} - content={
Modal Content
} - title="Modal Title" - /> - ) - - fireEvent.click(screen.getByText('Open Modal')) - expect(screen.getByText('Modal Title')).toBeInTheDocument() - }) - - it('renders full page modal', () => { - render( - Open Modal} - content={
Modal Content
} - fullPage - /> - ) - - fireEvent.click(screen.getByText('Open Modal')) - expect(screen.getByRole('dialog')).toHaveClass('modal__content--fullpage') - }) - - it('hides close button when hideClose is true', () => { - render( - Open Modal} - content={
Modal Content
} - hideClose - /> - ) - - fireEvent.click(screen.getByText('Open Modal')) - expect(screen.queryByLabelText('Close')).not.toBeInTheDocument() - }) - - it('calls onOpenChange when opening and closing the modal', () => { - const onOpenChangeMock = jest.fn() - render( - Open Modal} - content={
Modal Content
} - onOpenChange={onOpenChangeMock} - /> - ) - - fireEvent.click(screen.getByText('Open Modal')) - expect(onOpenChangeMock).toHaveBeenCalledWith(true) - - fireEvent.click(screen.getByLabelText('Close')) - expect(onOpenChangeMock).toHaveBeenCalledWith(false) - }) -}) diff --git a/joi/src/core/Modal/index.tsx b/joi/src/core/Modal/index.tsx deleted file mode 100644 index 7754fb3f0..000000000 --- a/joi/src/core/Modal/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { ReactNode } from 'react' -import * as DialogPrimitive from '@radix-ui/react-dialog' -import { Cross2Icon } from '@radix-ui/react-icons' - -import './styles.scss' -import { twMerge } from 'tailwind-merge' - -type Props = { - trigger?: ReactNode - content: ReactNode - open?: boolean - className?: string - fullPage?: boolean - hideClose?: boolean - title?: ReactNode - onOpenChange?: (open: boolean) => void -} - -const ModalClose = DialogPrimitive.Close - -const Modal = ({ - trigger, - content, - open, - title, - fullPage, - className, - onOpenChange, - hideClose, -}: Props) => ( - - {trigger} - - - - - {title} - - {content} - {!hideClose && ( - - - - )} - - - -) - -export { Modal, ModalClose } diff --git a/joi/src/core/Modal/styles.scss b/joi/src/core/Modal/styles.scss deleted file mode 100644 index 717ce2ac7..000000000 --- a/joi/src/core/Modal/styles.scss +++ /dev/null @@ -1,85 +0,0 @@ -/* reset */ -button, -fieldset, -.modal { - &__overlay { - background-color: hsla(var(--modal-overlay)); - z-index: 200; - position: fixed; - inset: 0; - animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1); - } - - &__content { - color: hsla(var(--modal-fg)); - overflow: auto; - background-color: hsla(var(--modal-bg)); - border-radius: 8px; - font-size: 14px; - position: fixed; - z-index: 300; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 50vw; - max-width: 560px; - max-height: 85vh; - padding: 16px; - animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1); - border: 1px solid hsla(var(--app-border)); - @apply w-full; - - &--fullpage { - max-width: none; - width: 90vw; - max-height: 90vh; - } - - &:focus { - outline: none; - } - } - - &__title { - @apply leading-relaxed; - margin: 0 0 8px 0; - padding-right: 16px; - font-weight: 600; - color: hsla(var(--modal-fg)); - font-size: 18px; - } - - &__close-icon { - font-family: inherit; - border-radius: 100%; - height: 24px; - width: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - color: hsla(var(--modal-fg)); - position: absolute; - top: 8px; - right: 16px; - } -} - -@keyframes overlayShow { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes contentShow { - from { - opacity: 0; - transform: translate(-50%, -48%) scale(0.96); - } - to { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } -} diff --git a/joi/src/core/Progress/Progress.test.tsx b/joi/src/core/Progress/Progress.test.tsx deleted file mode 100644 index 9d18bf019..000000000 --- a/joi/src/core/Progress/Progress.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Progress } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Progress', () => { - it('renders with default props', () => { - render() - const progressElement = screen.getByRole('progressbar') - expect(progressElement).toBeInTheDocument() - expect(progressElement).toHaveClass('progress') - expect(progressElement).toHaveClass('progress--medium') - expect(progressElement).toHaveAttribute('aria-valuenow', '50') - }) - - it('applies custom className', () => { - render() - const progressElement = screen.getByRole('progressbar') - expect(progressElement).toHaveClass('custom-class') - }) - - it('renders with different sizes', () => { - const { rerender } = render() - let progressElement = screen.getByRole('progressbar') - expect(progressElement).toHaveClass('progress--small') - - rerender() - progressElement = screen.getByRole('progressbar') - expect(progressElement).toHaveClass('progress--large') - }) - - it('sets the correct transform style based on value', () => { - render() - const progressElement = screen.getByRole('progressbar') - const indicatorElement = progressElement.firstChild as HTMLElement - expect(indicatorElement).toHaveStyle('transform: translateX(-25%)') - }) - - it('handles edge cases for value', () => { - const { rerender } = render() - let progressElement = screen.getByRole('progressbar') - let indicatorElement = progressElement.firstChild as HTMLElement - expect(indicatorElement).toHaveStyle('transform: translateX(-100%)') - expect(progressElement).toHaveAttribute('aria-valuenow', '0') - - rerender() - progressElement = screen.getByRole('progressbar') - indicatorElement = progressElement.firstChild as HTMLElement - expect(indicatorElement).toHaveStyle('transform: translateX(-0%)') - expect(progressElement).toHaveAttribute('aria-valuenow', '100') - }) -}) diff --git a/joi/src/core/Progress/index.tsx b/joi/src/core/Progress/index.tsx deleted file mode 100644 index 01aefbeb0..000000000 --- a/joi/src/core/Progress/index.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { HTMLAttributes } from 'react' - -import { cva, type VariantProps } from 'class-variance-authority' - -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -const progressVariants = cva('progress', { - variants: { - size: { - small: 'progress--small', - medium: 'progress--medium', - large: 'progress--large', - }, - }, - defaultVariants: { - size: 'medium', - }, -}) - -export interface ProgressProps - extends HTMLAttributes, - VariantProps { - value: number -} - -const Progress = ({ className, size, value, ...props }: ProgressProps) => { - return ( -
-
-
- ) -} - -export { Progress } diff --git a/joi/src/core/Progress/styles.scss b/joi/src/core/Progress/styles.scss deleted file mode 100644 index 02d22f5f4..000000000 --- a/joi/src/core/Progress/styles.scss +++ /dev/null @@ -1,25 +0,0 @@ -.progress { - background-color: hsla(var(--progress-track-bg)); - border-radius: 8px; - position: relative; - overflow: hidden; - @apply transition-all; - - &--indicator { - background-color: hsla(var(--primary-bg)); - position: absolute; - border-radius: 8px; - width: 100%; - height: 100%; - } - - &--small { - height: 6px; - } - &--medium { - @apply h-2; - } - &--large { - @apply h-3; - } -} diff --git a/joi/src/core/ScrollArea/ScrollArea.test.tsx b/joi/src/core/ScrollArea/ScrollArea.test.tsx deleted file mode 100644 index 961c5da59..000000000 --- a/joi/src/core/ScrollArea/ScrollArea.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { ScrollArea } from './index' - -declare const global: typeof globalThis - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -class ResizeObserverMock { - observe() {} - unobserve() {} - disconnect() {} -} - -global.ResizeObserver = ResizeObserverMock - -describe('@joi/core/ScrollArea', () => { - it('renders children correctly', () => { - render( - -
Test Content
-
- ) - - const child = screen.getByTestId('child') - expect(child).toBeInTheDocument() - expect(child).toHaveTextContent('Test Content') - }) - - it('applies custom className', () => { - const { container } = render() - - const root = container.firstChild as HTMLElement - expect(root).toHaveClass('scroll-area__root') - expect(root).toHaveClass('custom-class') - }) - - it('forwards ref to the Viewport component', () => { - const ref = React.createRef() - render() - - expect(ref.current).toBeInstanceOf(HTMLDivElement) - expect(ref.current).toHaveClass('scroll-area__viewport') - }) -}) diff --git a/joi/src/core/ScrollArea/index.tsx b/joi/src/core/ScrollArea/index.tsx deleted file mode 100644 index 2d44b4af8..000000000 --- a/joi/src/core/ScrollArea/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { PropsWithChildren, forwardRef } from 'react' -import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -const ScrollArea = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, onScroll, ...props }, ref) => ( - - - {children} - - - - - - - - - -)) - -export { ScrollArea } diff --git a/joi/src/core/ScrollArea/styles.scss b/joi/src/core/ScrollArea/styles.scss deleted file mode 100644 index 99ee7de87..000000000 --- a/joi/src/core/ScrollArea/styles.scss +++ /dev/null @@ -1,53 +0,0 @@ -.scroll-area { - position: relative; - z-index: 999; - - &__root { - width: 200px; - height: 225px; - overflow: hidden; - } - - &__viewport { - width: 100%; - height: 100%; - border-radius: inherit; - } - - &__bar { - display: flex; - user-select: none; - touch-action: none; - padding: 1px; - background: hsla(var(--scrollbar-tracker)); - transition: background 160ms ease-out; - } - - &__thumb { - flex: 1; - background: hsla(var(--scrollbar-thumb)); - border-radius: 20px; - position: relative; - - ::before { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 100%; - height: 100%; - min-width: 44px; - min-height: 44px; - } - } -} - -.scroll-area__bar[data-orientation='vertical'] { - width: 8px; -} - -.scroll-area__bar[data-orientation='horizontal'] { - flex-direction: column; - height: 8px; -} diff --git a/joi/src/core/Select/Select.test.tsx b/joi/src/core/Select/Select.test.tsx deleted file mode 100644 index 1b450706b..000000000 --- a/joi/src/core/Select/Select.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react' -import { render, screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { Select } from './index' -import '@testing-library/jest-dom' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -jest.mock('tailwind-merge', () => ({ - twMerge: (...classes: string[]) => classes.filter(Boolean).join(' '), -})) - -const mockOnValueChange = jest.fn() -jest.mock('@radix-ui/react-select', () => ({ - Root: ({ - children, - onValueChange, - }: { - children: React.ReactNode - onValueChange?: (value: string) => void - }) => { - mockOnValueChange.mockImplementation(onValueChange) - return
{children}
- }, - Trigger: ({ - children, - className, - }: { - children: React.ReactNode - className?: string - }) => ( - - ), - Value: ({ placeholder }: { placeholder?: string }) => ( - {placeholder} - ), - Icon: ({ children }: { children: React.ReactNode }) => ( - {children} - ), - Portal: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), - Content: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), - Viewport: ({ children }: { children: React.ReactNode }) => ( -
{children}
- ), - Item: ({ children, value }: { children: React.ReactNode; value: string }) => ( -
mockOnValueChange(value)} - > - {children} -
- ), - ItemText: ({ children }: { children: React.ReactNode }) => ( - {children} - ), - ItemIndicator: ({ children }: { children: React.ReactNode }) => ( - {children} - ), - Arrow: () =>
, -})) -describe('@joi/core/Select', () => { - const options = [ - { name: 'Option 1', value: 'option1' }, - { name: 'Option 2', value: 'option2' }, - ] - - it('renders with placeholder', () => { - render() - expect(screen.getByTestId('select-item-option1')).toBeInTheDocument() - expect(screen.getByTestId('select-item-option2')).toBeInTheDocument() - }) - - it('calls onValueChange when an option is selected', async () => { - const user = userEvent.setup() - const onValueChange = jest.fn() - render() - expect(screen.getByTestId('select-trigger')).toHaveClass('select__disabled') - }) - - it('applies block class when block prop is true', () => { - render( - {children} -
- ), - Track: ({ children }: any) => ( -
{children}
- ), - Range: () =>
, - Thumb: () =>
, -})) - -describe('@joi/core/Slider', () => { - it('renders correctly with default props', () => { - render() - expect(screen.getByTestId('slider-root')).toBeInTheDocument() - expect(screen.getByTestId('slider-track')).toBeInTheDocument() - expect(screen.getByTestId('slider-range')).toBeInTheDocument() - expect(screen.getByTestId('slider-thumb')).toBeInTheDocument() - }) - - it('passes props correctly to SliderPrimitive.Root', () => { - const props = { - name: 'test-slider', - min: 0, - max: 100, - value: [50], - step: 1, - disabled: true, - } - render() - const sliderRoot = screen.getByTestId('slider-root') - expect(sliderRoot).toHaveAttribute('name', 'test-slider') - expect(sliderRoot).toHaveAttribute('min', '0') - expect(sliderRoot).toHaveAttribute('max', '100') - expect(sliderRoot).toHaveAttribute('value', '50') - expect(sliderRoot).toHaveAttribute('step', '1') - expect(sliderRoot).toHaveAttribute('disabled', '') - }) - - it('calls onValueChange when value changes', () => { - const onValueChange = jest.fn() - render() - const input = screen.getByTestId('slider-root').querySelector('input') - fireEvent.change(input!, { target: { value: '75' } }) - expect(onValueChange).toHaveBeenCalledWith([75]) - }) -}) diff --git a/joi/src/core/Slider/index.tsx b/joi/src/core/Slider/index.tsx deleted file mode 100644 index ea3d8dfca..000000000 --- a/joi/src/core/Slider/index.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react' -import * as SliderPrimitive from '@radix-ui/react-slider' -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -type Props = { - name?: string - min?: number - max?: number - onValueChange?(value: number[]): void - value?: number[] - defaultValue?: number[] - step?: number - disabled?: boolean -} - -const Slider = ({ - name, - min, - max, - onValueChange, - value, - defaultValue, - step, - disabled, -}: Props) => ( - - - - - {value?.map((_, i) => ( - - ))} - -) - -export { Slider } diff --git a/joi/src/core/Slider/styles.scss b/joi/src/core/Slider/styles.scss deleted file mode 100644 index 6b7cf8575..000000000 --- a/joi/src/core/Slider/styles.scss +++ /dev/null @@ -1,43 +0,0 @@ -.slider { - position: relative; - display: flex; - align-items: center; - user-select: none; - touch-action: none; - height: 16px; - - &--disabled { - cursor: not-allowed; - opacity: 0.2; - } - - &__track { - background-color: hsla(var(--slider-track-bg)); - position: relative; - flex-grow: 1; - border-radius: 9999px; - height: 4px; - } - - &__range { - position: absolute; - background-color: hsla(var(--primary-bg)); - border-radius: 9999px; - height: 100%; - } - - &__thumb { - display: block; - width: 16px; - height: 16px; - background-color: hsla(var(--slider-thumb-bg)); - border-radius: 10px; - padding: 2px; - border: 2px solid hsla(var(--primary-bg)); - - &:focus { - outline: none; - box-shadow: 0 0 0 5px hsla(var(--slider-track-bg), 50%); - } - } -} diff --git a/joi/src/core/Switch/Switch.test.tsx b/joi/src/core/Switch/Switch.test.tsx deleted file mode 100644 index 72f3d8007..000000000 --- a/joi/src/core/Switch/Switch.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react' -import { render, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Switch } from './index' - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Switch', () => { - it('renders correctly', () => { - const { getByRole } = render() - const checkbox = getByRole('checkbox') - expect(checkbox).toBeInTheDocument() - }) - - it('applies custom className', () => { - const { container } = render() - expect(container.firstChild).toHaveClass('switch custom-class') - }) - - it('can be checked and unchecked', () => { - const { getByRole } = render() - const checkbox = getByRole('checkbox') as HTMLInputElement - - expect(checkbox.checked).toBe(false) - fireEvent.click(checkbox) - expect(checkbox.checked).toBe(true) - fireEvent.click(checkbox) - expect(checkbox.checked).toBe(false) - }) - - it('can be disabled', () => { - const { getByRole } = render() - const checkbox = getByRole('checkbox') as HTMLInputElement - expect(checkbox).toBeDisabled() - }) - - it('calls onChange when clicked', () => { - const handleChange = jest.fn() - const { getByRole } = render() - const checkbox = getByRole('checkbox') - - fireEvent.click(checkbox) - expect(handleChange).toHaveBeenCalledTimes(1) - }) - - it('can have a default checked state', () => { - const { getByRole } = render() - const checkbox = getByRole('checkbox') as HTMLInputElement - expect(checkbox.checked).toBe(true) - }) -}) diff --git a/joi/src/core/Switch/index.tsx b/joi/src/core/Switch/index.tsx deleted file mode 100644 index 28eabe6e6..000000000 --- a/joi/src/core/Switch/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { ChangeEvent, InputHTMLAttributes } from 'react' - -import { twMerge } from 'tailwind-merge' - -import './styles.scss' - -export interface SwitchProps extends InputHTMLAttributes { - disabled?: boolean - className?: string - onChange?: (e: ChangeEvent) => void -} - -const Switch = ({ - name, - checked, - disabled, - defaultChecked, - className, - onChange, - ...props -}: SwitchProps) => { - return ( - - ) -} -export { Switch } diff --git a/joi/src/core/Switch/styles.scss b/joi/src/core/Switch/styles.scss deleted file mode 100644 index 9f7adbd4f..000000000 --- a/joi/src/core/Switch/styles.scss +++ /dev/null @@ -1,67 +0,0 @@ -.switch { - position: relative; - display: inline-block; - width: 32px; - height: 18px; - - > input { - opacity: 0; - width: 0; - height: 0; - - // disabled - &:disabled { - + .switch--thumb { - cursor: not-allowed; - background-color: hsla(var(--disabled-bg)); - &:before { - background-color: hsla(var(--disabled-fg)); - } - } - // disabled and checked - &:checked + .switch--thumb { - cursor: not-allowed; - background-color: hsla(var(--primary-bg)); - &:before { - background-color: hsla(var(--disabled-fg)); - } - } - } - - &:checked + .switch--thumb { - background-color: hsla(var(--primary-bg)); - - &::before { - -webkit-transform: translateX(14px); - -ms-transform: translateX(14px); - transform: translateX(14px); - } - } - } - - &--thumb { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: hsla(var(--switch-bg)); - -webkit-transition: 0.4s; - transition: 0.4s; - border-radius: 20px; - - &:before { - position: absolute; - content: ''; - height: 14px; - width: 14px; - left: 2px; - bottom: 2px; - background-color: hsla(var(--switch-fg)); - -webkit-transition: 0.4s; - transition: 0.4s; - border-radius: 50%; - } - } -} diff --git a/joi/src/core/Tabs/Tabs.test.tsx b/joi/src/core/Tabs/Tabs.test.tsx deleted file mode 100644 index 46bd48435..000000000 --- a/joi/src/core/Tabs/Tabs.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from 'react' -import { render, screen, fireEvent } from '@testing-library/react' -import '@testing-library/jest-dom' -import { Tabs, TabsContent } from './index' - -// Mock the Tooltip component -jest.mock('../Tooltip', () => ({ - Tooltip: ({ children, content, trigger }) => ( -
- {trigger || children} -
- ), -})) - -// Mock the styles -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/Tabs', () => { - const mockOptions = [ - { name: 'Tab 1', value: 'tab1' }, - { name: 'Tab 2', value: 'tab2' }, - { - name: 'Tab 3', - value: 'tab3', - disabled: true, - tooltipContent: 'Disabled tab', - }, - ] - - it('renders tabs correctly', () => { - render( - {}}> - Content 1 - Content 2 - Content 3 - - ) - - expect(screen.getByText('Tab 1')).toBeInTheDocument() - expect(screen.getByText('Tab 2')).toBeInTheDocument() - expect(screen.getByText('Tab 3')).toBeInTheDocument() - expect(screen.getByText('Content 1')).toBeInTheDocument() - }) - - it('changes tab content when clicked', () => { - const { rerender } = render( - {}}> - Content 1 - Content 2 - Content 3 - - ) - - expect(screen.getByText('Content 1')).toBeInTheDocument() - expect(screen.queryByText('Content 2')).not.toBeInTheDocument() - - fireEvent.click(screen.getByText('Tab 2')) - - // Rerender with the new value to simulate the state change - rerender( - {}}> - Content 1 - Content 2 - Content 3 - - ) - - expect(screen.queryByText('Content 1')).not.toBeInTheDocument() - expect(screen.getByText('Content 2')).toBeInTheDocument() - }) - - it('disables tab when specified', () => { - render( - {}}> - Content 1 - Content 2 - Content 3 - - ) - - expect(screen.getByText('Tab 3')).toHaveAttribute('disabled') - }) - - it('renders tooltip for disabled tab', () => { - render( - {}}> - Content 1 - Content 2 - Content 3 - - ) - - const tooltipWrapper = screen.getByTestId('mock-tooltip') - expect(tooltipWrapper).toHaveAttribute( - 'data-tooltip-content', - 'Disabled tab' - ) - }) - - it('applies the tabStyle if provided', () => { - render( - {}} - tabStyle="segmented" - /> - ) - - const tabsContainer = screen.getByTestId('segmented-style') - expect(tabsContainer).toHaveClass('tabs') - expect(tabsContainer).toHaveClass('tabs--segmented') - }) -}) diff --git a/joi/src/core/Tabs/index.tsx b/joi/src/core/Tabs/index.tsx deleted file mode 100644 index 2dca19831..000000000 --- a/joi/src/core/Tabs/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { ReactNode } from 'react' - -import * as TabsPrimitive from '@radix-ui/react-tabs' - -import { Tooltip } from '../Tooltip' - -import './styles.scss' -import { twMerge } from 'tailwind-merge' - -type TabStyles = 'segmented' - -type TabsProps = { - options: { - name: string - value: string - disabled?: boolean - tooltipContent?: string - }[] - children?: ReactNode - - defaultValue?: string - tabStyle?: TabStyles - value: string - onValueChange?: (value: string) => void -} - -type TabsContentProps = { - value: string - children: ReactNode - className?: string -} - -const TabsContent = ({ value, children, className }: TabsContentProps) => { - return ( - - {children} - - ) -} - -const Tabs = ({ - options, - children, - tabStyle, - defaultValue, - value, - onValueChange, - ...props -}: TabsProps) => ( - - - {options.map((option, i) => { - return option.disabled ? ( - - {option.name} - - } - /> - ) : ( - - {option.name} - - ) - })} - - - {children} - -) - -export { Tabs, TabsContent } diff --git a/joi/src/core/Tabs/styles.scss b/joi/src/core/Tabs/styles.scss deleted file mode 100644 index 932b8431a..000000000 --- a/joi/src/core/Tabs/styles.scss +++ /dev/null @@ -1,66 +0,0 @@ -.tabs { - display: flex; - flex-direction: column; - width: 100%; - - &--segmented { - background-color: hsla(var(--secondary-bg)); - border-radius: 6px; - height: 33px; - - .tabs__list { - border: none; - justify-content: center; - align-items: center; - height: 33px; - } - - .tabs__trigger[data-state='active'] { - background-color: hsla(var(--app-bg)); - border: none; - height: 25px; - margin: 0 4px; - border-radius: 5px; - } - } - - &__list { - flex-shrink: 0; - display: flex; - border-bottom: 1px solid hsla(var(--app-border)); - } - - &__trigger { - padding: 0 12px; - flex: 1; - height: 38px; - display: flex; - white-space: nowrap; - color: hsla(var(--text-secondary)); - align-items: center; - justify-content: center; - line-height: 1; - font-weight: medium; - user-select: none; - &:focus { - position: relative; - } - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } - } - - &__content { - flex-grow: 1; - border-bottom-left-radius: 6px; - border-bottom-right-radius: 6px; - outline: none; - } -} - -.tabs__trigger[data-state='active'] { - border-bottom: 1px solid hsla(var(--primary-bg)); - font-weight: 600; - color: hsla(var(--text-primary)); -} diff --git a/joi/src/core/TextArea/TextArea.test.tsx b/joi/src/core/TextArea/TextArea.test.tsx deleted file mode 100644 index e29eed5d0..000000000 --- a/joi/src/core/TextArea/TextArea.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react' -import { render, screen, act } from '@testing-library/react' -import '@testing-library/jest-dom' -import { TextArea } from './index' - -jest.mock('./styles.scss', () => ({})) - -describe('@joi/core/TextArea', () => { - it('renders correctly', () => { - render(