diff --git a/.devcontainer/buildAppImage.sh b/.devcontainer/buildAppImage.sh
deleted file mode 100644
index bdd9365bc..000000000
--- a/.devcontainer/buildAppImage.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-
-make clean
-
-# To reproduce https://github.com/menloresearch/jan/pull/5463
-TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri"
-mkdir -p "$TAURI_TOOLKIT_PATH"
-wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
-chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
-
-jq '.bundle.resources = ["resources/pre-install/**/*"] | .bundle.externalBin = ["resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
-mv /tmp/tauri.conf.json ./src-tauri/tauri.conf.json
-
-make build-tauri
-
-cp ./src-tauri/resources/bin/bun ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/bin/bun
-cp -f ./src-tauri/binaries/*.so* ./src-tauri/target/release/bundle/appimage/Jan.AppDir/usr/lib/Jan/binaries/
-APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
-echo $APP_IMAGE
-rm -f $APP_IMAGE
-/opt/bin/appimagetool ./src-tauri/target/release/bundle/appimage/Jan.AppDir $APP_IMAGE
\ No newline at end of file
diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh
index 79fb4de1c..a9a1277b8 100755
--- a/.devcontainer/postCreateCommand.sh
+++ b/.devcontainer/postCreateCommand.sh
@@ -14,7 +14,3 @@ sudo apt install -yqq libwebkit2gtk-4.1-dev \
librsvg2-dev \
xdg-utils \
libfuse2
-
-sudo mkdir -p /opt/bin
-sudo wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O /opt/bin/appimagetool
-sudo chmod +x /opt/bin/appimagetool
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/2-feature-request.md b/.github/ISSUE_TEMPLATE/2-feature-request.md
index 3a6c97232..4da9e8450 100644
--- a/.github/ISSUE_TEMPLATE/2-feature-request.md
+++ b/.github/ISSUE_TEMPLATE/2-feature-request.md
@@ -2,6 +2,7 @@
name: 🚀 Feature Request
about: Suggest an idea for this project 😻!
title: 'idea: '
+type: Idea
---
## Problem Statement
diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml
deleted file mode 100644
index b4d275658..000000000
--- a/.github/workflows/jan-electron-build-nightly.yml
+++ /dev/null
@@ -1,215 +0,0 @@
-name: Electron Builder - Nightly / Manual
-
-on:
- schedule:
- - cron: '0 20 * * 1,2,3' # At 8 PM UTC on Monday, Tuesday, and Wednesday which is 3 AM UTC+7 Tuesday, Wednesday, and Thursday
- workflow_dispatch:
- inputs:
- public_provider:
- type: choice
- description: 'Public Provider'
- options:
- - none
- - aws-s3
- default: none
- pull_request:
- branches:
- - release/**
-
-jobs:
- set-public-provider:
- runs-on: ubuntu-latest
- outputs:
- public_provider: ${{ steps.set-public-provider.outputs.public_provider }}
- ref: ${{ steps.set-public-provider.outputs.ref }}
- steps:
- - name: Set public provider
- id: set-public-provider
- run: |
- if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
- echo "::set-output name=public_provider::${{ github.event.inputs.public_provider }}"
- echo "::set-output name=ref::${{ github.ref }}"
- else
- if [ "${{ github.event_name }}" == "schedule" ]; then
- echo "::set-output name=public_provider::aws-s3"
- echo "::set-output name=ref::refs/heads/dev"
- elif [ "${{ github.event_name }}" == "push" ]; then
- echo "::set-output name=public_provider::aws-s3"
- echo "::set-output name=ref::${{ github.ref }}"
- elif [ "${{ github.event_name }}" == "pull_request_review" ]; then
- echo "::set-output name=public_provider::none"
- echo "::set-output name=ref::${{ github.ref }}"
- else
- echo "::set-output name=public_provider::none"
- echo "::set-output name=ref::${{ github.ref }}"
- fi
- fi
- # Job create Update app version based on latest release tag with build number and save to output
- get-update-version:
- uses: ./.github/workflows/template-get-update-version.yml
-
- build-tauri-macos:
- uses: ./.github/workflows/template-tauri-build-macos.yml
- secrets: inherit
- needs: [get-update-version, set-public-provider]
- with:
- ref: ${{ needs.set-public-provider.outputs.ref }}
- public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
- new_version: ${{ needs.get-update-version.outputs.new_version }}
- channel: nightly
- cortex_api_port: "39261"
-
- build-tauri-windows-x64:
- uses: ./.github/workflows/template-tauri-build-windows-x64.yml
- secrets: inherit
- needs: [get-update-version, set-public-provider]
- with:
- ref: ${{ needs.set-public-provider.outputs.ref }}
- public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
- new_version: ${{ needs.get-update-version.outputs.new_version }}
- channel: nightly
- cortex_api_port: "39261"
-
- build-tauri-linux-x64:
- uses: ./.github/workflows/template-tauri-build-linux-x64.yml
- secrets: inherit
- needs: [get-update-version, set-public-provider]
- with:
- ref: ${{ needs.set-public-provider.outputs.ref }}
- public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
- new_version: ${{ needs.get-update-version.outputs.new_version }}
- channel: nightly
- cortex_api_port: "39261"
-
- sync-temp-to-latest:
- needs: [get-update-version, set-public-provider, build-tauri-windows-x64, build-tauri-linux-x64, build-tauri-macos]
- runs-on: ubuntu-latest
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- - name: Install jq
- uses: dcarbone/install-jq-action@v2.0.1
- - name: create latest.json file
- run: |
- VERSION=${{ needs.get-update-version.outputs.new_version }}
- PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%S.%3NZ")
- LINUX_SIGNATURE="${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_SIG }}"
- LINUX_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-linux-x64.outputs.APPIMAGE_FILE_NAME }}"
- WINDOWS_SIGNATURE="${{ needs.build-tauri-windows-x64.outputs.WIN_SIG }}"
- WINDOWS_URL="https://delta.jan.ai/nightly/${{ needs.build-tauri-windows-x64.outputs.FILE_NAME }}"
- DARWIN_SIGNATURE="${{ needs.build-tauri-macos.outputs.MAC_UNIVERSAL_SIG }}"
- DARWIN_URL="https://delta.jan.ai/nightly/Jan-nightly_${{ needs.get-update-version.outputs.new_version }}.app.tar.gz"
-
- jq --arg version "$VERSION" \
- --arg pub_date "$PUB_DATE" \
- --arg linux_signature "$LINUX_SIGNATURE" \
- --arg linux_url "$LINUX_URL" \
- --arg windows_signature "$WINDOWS_SIGNATURE" \
- --arg windows_url "$WINDOWS_URL" \
- --arg darwin_arm_signature "$DARWIN_SIGNATURE" \
- --arg darwin_arm_url "$DARWIN_URL" \
- --arg darwin_amd_signature "$DARWIN_SIGNATURE" \
- --arg darwin_amd_url "$DARWIN_URL" \
- '.version = $version
- | .pub_date = $pub_date
- | .platforms["linux-x86_64"].signature = $linux_signature
- | .platforms["linux-x86_64"].url = $linux_url
- | .platforms["windows-x86_64"].signature = $windows_signature
- | .platforms["windows-x86_64"].url = $windows_url
- | .platforms["darwin-aarch64"].signature = $darwin_arm_signature
- | .platforms["darwin-aarch64"].url = $darwin_arm_url
- | .platforms["darwin-x86_64"].signature = $darwin_amd_signature
- | .platforms["darwin-x86_64"].url = $darwin_amd_url' \
- src-tauri/latest.json.template > latest.json
- cat latest.json
- - name: Sync temp to latest
- if: ${{ needs.set-public-provider.outputs.public_provider == 'aws-s3' }}
- run: |
- aws s3 cp ./latest.json s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/latest.json
- aws s3 sync s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/temp-nightly/ s3://${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}/nightly/
- env:
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
- AWS_EC2_METADATA_DISABLED: "true"
-
- noti-discord-nightly-and-update-url-readme:
- needs: [
- build-tauri-macos,
- build-tauri-windows-x64,
- build-tauri-linux-x64,
- get-update-version,
- set-public-provider,
- sync-temp-to-latest
- ]
- secrets: inherit
- if: github.event_name == 'schedule'
- uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
- with:
- ref: refs/heads/dev
- build_reason: Nightly
- push_to_branch: dev
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
- noti-discord-pre-release-and-update-url-readme:
- needs: [
- build-tauri-macos,
- build-tauri-windows-x64,
- build-tauri-linux-x64,
- get-update-version,
- set-public-provider,
- sync-temp-to-latest
- ]
- secrets: inherit
- if: github.event_name == 'push'
- uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
- with:
- ref: refs/heads/dev
- build_reason: Pre-release
- push_to_branch: dev
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
- noti-discord-manual-and-update-url-readme:
- needs: [
- build-tauri-macos,
- build-tauri-windows-x64,
- build-tauri-linux-x64,
- get-update-version,
- set-public-provider,
- sync-temp-to-latest
- ]
- secrets: inherit
- if: github.event_name == 'workflow_dispatch' && github.event.inputs.public_provider == 'aws-s3'
- uses: ./.github/workflows/template-noti-discord-and-update-url-readme.yml
- with:
- ref: refs/heads/dev
- build_reason: Manual
- push_to_branch: dev
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
-
- # comment-pr-build-url:
- # needs: [
- # build-tauri-macos,
- # build-tauri-windows-x64,
- # build-tauri-linux-x64,
- # get-update-version,
- # set-public-provider,
- # sync-temp-to-latest
- # ]
- # runs-on: ubuntu-latest
- # if: github.event_name == 'pull_request_review'
- # steps:
- # - name: Set up GitHub CLI
- # run: |
- # curl -sSL https://github.com/cli/cli/releases/download/v2.33.0/gh_2.33.0_linux_amd64.tar.gz | tar xz
- # sudo cp gh_2.33.0_linux_amd64/bin/gh /usr/local/bin/
-
- # - name: Comment build URL on PR
- # env:
- # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- # run: |
- # PR_URL=${{ github.event.pull_request.html_url }}
- # RUN_ID=${{ github.run_id }}
- # COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})."
- # gh pr comment $PR_URL --body "$COMMENT"
\ No newline at end of file
diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml
deleted file mode 100644
index a223027f3..000000000
--- a/.github/workflows/jan-electron-build.yml
+++ /dev/null
@@ -1,131 +0,0 @@
-name: Electron Builder - Tag
-
-on:
- push:
- tags: ["v[0-9]+.[0-9]+.[0-9]+"]
-
-jobs:
- # Job create Update app version based on latest release tag with build number and save to output
- get-update-version:
- uses: ./.github/workflows/template-get-update-version.yml
-
- create-draft-release:
- runs-on: ubuntu-latest
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
- outputs:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- version: ${{ steps.get_version.outputs.version }}
- permissions:
- contents: write
- steps:
- - name: Extract tag name without v prefix
- id: get_version
- run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
- env:
- GITHUB_REF: ${{ github.ref }}
- - name: Create Draft Release
- id: create_release
- uses: softprops/action-gh-release@v2
- with:
- tag_name: ${{ github.ref_name }}
- token: ${{ secrets.GITHUB_TOKEN }}
- name: "${{ env.VERSION }}"
- draft: true
- prerelease: false
-
- build-electron-macos:
- uses: ./.github/workflows/template-electron-build-macos.yml
- secrets: inherit
- needs: [get-update-version]
- with:
- ref: ${{ github.ref }}
- public_provider: github
- beta: false
- nightly: false
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
- build-electron-windows-x64:
- uses: ./.github/workflows/template-electron-build-windows-x64.yml
- secrets: inherit
- needs: [get-update-version]
- with:
- ref: ${{ github.ref }}
- public_provider: github
- beta: false
- nightly: false
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
- build-electron-linux-x64:
- uses: ./.github/workflows/template-electron-build-linux-x64.yml
- secrets: inherit
- needs: [get-update-version]
- with:
- ref: ${{ github.ref }}
- public_provider: github
- beta: false
- nightly: false
- new_version: ${{ needs.get-update-version.outputs.new_version }}
-
- # build-tauri-macos:
- # uses: ./.github/workflows/template-tauri-build-macos.yml
- # secrets: inherit
- # needs: [get-update-version, create-draft-release]
- # with:
- # ref: ${{ github.ref }}
- # public_provider: github
- # channel: stable
- # new_version: ${{ needs.get-update-version.outputs.new_version }}
- # upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
-
- # build-tauri-windows-x64:
- # uses: ./.github/workflows/template-tauri-build-windows-x64.yml
- # secrets: inherit
- # needs: [get-update-version, create-draft-release]
- # with:
- # ref: ${{ github.ref }}
- # public_provider: github
- # channel: stable
- # new_version: ${{ needs.get-update-version.outputs.new_version }}
- # upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
-
- # build-tauri-linux-x64:
- # uses: ./.github/workflows/template-tauri-build-linux-x64.yml
- # secrets: inherit
- # needs: [get-update-version, create-draft-release]
- # with:
- # ref: ${{ github.ref }}
- # public_provider: github
- # channel: stable
- # new_version: ${{ needs.get-update-version.outputs.new_version }}
- # upload_url: ${{ needs.create-draft-release.outputs.upload_url }}
-
- update_release_draft:
- needs: [
- build-electron-windows-x64,
- build-electron-linux-x64,
- build-electron-macos,
- build-tauri-windows-x64,
- build-tauri-linux-x64,
- build-tauri-macos
- ]
- permissions:
- # write permission is required to create a github release
- contents: write
- # write permission is required for autolabeler
- # otherwise, read permission is required at least
- pull-requests: write
- runs-on: ubuntu-latest
- steps:
- # (Optional) GitHub Enterprise requires GHE_HOST variable set
- #- name: Set GHE_HOST
- # run: |
- # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV
-
- # Drafts your next Release notes as Pull Requests are merged into "master"
- - uses: release-drafter/release-drafter@v5
- # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml
- # with:
- # config-name: my-config.yml
- # disable-autolabeler: true
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml
deleted file mode 100644
index e895baf06..000000000
--- a/.github/workflows/jan-electron-linter-and-test.yml
+++ /dev/null
@@ -1,435 +0,0 @@
-name: Test - Linter & Playwright
-on:
- workflow_dispatch:
- push:
- branches:
- - main
- - dev
- paths:
- - 'electron/**'
- - .github/workflows/jan-electron-linter-and-test.yml
- - 'web/**'
- - 'joi/**'
- - 'package.json'
- - 'node_modules/**'
- - 'yarn.lock'
- - 'core/**'
- - 'extensions/**'
- - '!README.md'
- - 'Makefile'
-
- pull_request:
- branches:
- - main
- - dev
- - release/**
- paths:
- - 'electron/**'
- - .github/workflows/jan-electron-linter-and-test.yml
- - 'web/**'
- - 'joi/**'
- - 'package.json'
- - 'node_modules/**'
- - 'yarn.lock'
- - 'Makefile'
- - 'extensions/**'
- - 'core/**'
- - 'src-tauri/**'
- - 'web-app/**'
- - '!README.md'
-
-jobs:
- base_branch_cov:
- runs-on: ubuntu-latest
- continue-on-error: true
- steps:
- - uses: actions/checkout@v3
- with:
- ref: ${{ github.base_ref }}
- - name: Use Node.js 20.x
- uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: Install dependencies
- run: |
- make config-yarn
- yarn
- yarn build:core
-
- - name: Run test coverage
- run: yarn test:coverage
-
- - name: Upload code coverage for ref branch
- uses: actions/upload-artifact@v4
- with:
- name: ref-lcov.info
- path: ./coverage/lcov.info
-
- test-on-macos:
- if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
- runs-on: macos-latest
- 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: Set IS_TEST environment variable
- run: |
- echo "IS_TEST=true" >> $GITHUB_ENV
-
- - name: 'Cleanup cache'
- continue-on-error: true
- run: |
- rm -rf ~/jan
- make clean
-
- - name: Get Commit Message for PR
- if: github.event_name == 'pull_request'
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV
-
- - name: Get Commit Message for push event
- if: github.event_name == 'push'
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV
-
- # - name: 'Config report portal'
- # run: |
- # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
-
- - name: Linter and test
- run: |
- make test
- env:
- CSC_IDENTITY_AUTO_DISCOVERY: 'false'
-
- test-on-macos-pr-target:
- if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
- runs-on: macos-latest
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - name: Installing node
- uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: 'Cleanup cache'
- continue-on-error: true
- run: |
- rm -rf ~/jan
- make clean
-
- - name: Linter and test
- run: |
- make test
- env:
- CSC_IDENTITY_AUTO_DISCOVERY: 'false'
-
- test-on-windows:
- if: github.event_name == 'push'
- strategy:
- fail-fast: false
- matrix:
- antivirus-tools: ['mcafee', 'default-windows-security', 'bit-defender']
- runs-on: windows-desktop-${{ matrix.antivirus-tools }}
- 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: Install tauri-driver dependencies
- run: |
- cargo install tauri-driver --locked
-
- # Clean cache, continue on error
- - name: 'Cleanup cache'
- shell: powershell
- continue-on-error: true
- run: |
- $path = "$Env:APPDATA\jan"
- if (Test-Path $path) {
- Remove-Item "\\?\$path" -Recurse -Force
- } else {
- Write-Output "Folder does not exist."
- }
- make clean
-
- - name: Get Commit Message for push event
- if: github.event_name == 'push'
- shell: bash
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
-
- # - name: 'Config report portal'
- # shell: bash
- # run: |
- # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
-
- - name: Linter and test
- shell: powershell
- run: |
- make test
-
- test-on-windows-pr:
- if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'workflow_dispatch'
- runs-on: windows-latest
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - uses: actions/cache@v4 # v4
- with:
- path: |
- ~/.cargo/bin/
- ~/.cargo/registry/index/
- ~/.cargo/registry/cache/
- ~/.cargo/git/db/
- target/
- key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/yarn.lock') }}
-
- - name: Installing node
- uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: Install tauri-driver dependencies
- run: |
- cargo install tauri-driver --locked
-
- # Clean cache, continue on error
- - name: 'Cleanup cache'
- shell: powershell
- continue-on-error: true
- run: |
- $path = "$Env:APPDATA\jan"
- if (Test-Path $path) {
- Remove-Item "\\?\$path" -Recurse -Force
- } else {
- Write-Output "Folder does not exist."
- }
- make clean
-
- - name: Get Commit Message for PR
- if: github.event_name == 'pull_request'
- shell: bash
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
-
- # - name: 'Config report portal'
- # shell: bash
- # run: |
- # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
-
- - name: Install Prerequisites
- shell: 'powershell'
- # https://github.com/actions/runner-images/issues/9538
- # https://github.com/microsoft/playwright/pull/30009/files
- # https://github.com/tauri-apps/wry/issues/1268
- # Evergreen Bootstrapper
- # The Bootstrapper is a tiny installer that downloads
- # the Evergreen Runtime matching device architecture
- # and installs it locally.
- # https://developer.microsoft.com/en-us/microsoft-edge/webview2/consumer/?form=MA13LH
- run: |
- Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
- Start-Process -FilePath setup.exe -Verb RunAs -Wait
-
- - name: Linter and test
- shell: powershell
- run: |
- make test
-
- test-on-windows-pr-target:
- if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
- runs-on: windows-latest
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - name: Installing node
- uses: actions/setup-node@v1
- with:
- node-version: 20
-
- - name: Install tauri-driver dependencies
- run: |
- cargo install tauri-driver --locked
-
- # Clean cache, continue on error
- - name: 'Cleanup cache'
- shell: powershell
- continue-on-error: true
- run: |
- $path = "$Env:APPDATA\jan"
- if (Test-Path $path) {
- Remove-Item "\\?\$path" -Recurse -Force
- } else {
- Write-Output "Folder does not exist."
- }
- make clean
-
- - name: Linter and test
- shell: powershell
- run: |
- make test
-
- test-on-ubuntu:
- runs-on: ubuntu-latest
- if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
- 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: Install Tauri dependencies
- run: |
- sudo apt update
- sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
-
- - name: Install tauri-driver dependencies
- run: |
- cargo install tauri-driver --locked
-
- - name: 'Cleanup cache'
- continue-on-error: true
- run: |
- rm -rf ~/jan
- make clean
-
- - name: Get Commit Message for PR
- if: github.event_name == 'pull_request'
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
-
- - name: Get Commit Message for push event
- if: github.event_name == 'push'
- run: |
- echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
-
- # - name: 'Config report portal'
- # shell: bash
- # run: |
- # make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
-
- - name: Linter and test
- run: |
- export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
- echo -e "Display ID: $DISPLAY"
- make test
-
- - uses: actions/upload-artifact@v4
- if: always()
- with:
- name: playwright-report
- path: electron/playwright-report/
- retention-days: 2
-
- # 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: Install yarn
- # run: npm install -g yarn
-
- # - 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: 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'
-
- test-on-ubuntu-pr-target:
- runs-on: ubuntu-latest
- if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - name: Installing node
- uses: actions/setup-node@v3
- with:
- node-version: 20
-
- - name: Install Tauri dependencies
- run: |
- sudo apt update
- sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
-
- - name: Install tauri-driver dependencies
- run: |
- cargo install tauri-driver --locked
-
- - name: 'Cleanup cache'
- continue-on-error: true
- run: |
- rm -rf ~/jan
- make clean
-
- - name: Linter and test
- run: |
- export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
- echo -e "Display ID: $DISPLAY"
- make test
diff --git a/.github/workflows/jan-linter-and-test.yml b/.github/workflows/jan-linter-and-test.yml
new file mode 100644
index 000000000..e09c23f04
--- /dev/null
+++ b/.github/workflows/jan-linter-and-test.yml
@@ -0,0 +1,269 @@
+name: Test - Linter & Playwright
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - dev
+ paths:
+ - .github/workflows/jan-linter-and-test.yml
+ - 'web/**'
+ - 'joi/**'
+ - 'package.json'
+ - 'node_modules/**'
+ - 'yarn.lock'
+ - 'core/**'
+ - 'extensions/**'
+ - '!README.md'
+ - 'Makefile'
+
+ pull_request:
+ branches:
+ - main
+ - dev
+ - release/**
+ paths:
+ - .github/workflows/jan-linter-and-test.yml
+ - 'web/**'
+ - 'joi/**'
+ - 'package.json'
+ - 'node_modules/**'
+ - 'yarn.lock'
+ - 'Makefile'
+ - 'extensions/**'
+ - 'core/**'
+ - 'src-tauri/**'
+ - 'web-app/**'
+ - '!README.md'
+
+jobs:
+ base_branch_cov:
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
+ runs-on: ubuntu-latest
+ continue-on-error: true
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ ref: ${{ github.base_ref }}
+ - name: Use Node.js 20.x
+ uses: actions/setup-node@v3
+ with:
+ node-version: 20
+
+ - name: 'Cleanup cache'
+ continue-on-error: true
+ run: |
+ rm -rf ~/jan
+ make clean
+
+ - name: Install dependencies
+ run: |
+ make lint
+
+ - name: Run test coverage
+ run: |
+ yarn test:coverage
+
+ - name: Upload code coverage for ref branch
+ uses: actions/upload-artifact@v4
+ with:
+ name: ref-lcov.info
+ path: coverage/merged/lcov.info
+
+ test-on-macos:
+ runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'macos-latest' || 'macos-selfhosted-12-arm64' }}
+ if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ steps:
+ - name: Getting the repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Installing node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 20
+
+ - name: 'Cleanup cache'
+ continue-on-error: true
+ run: |
+ rm -rf ~/jan
+ make clean
+
+ - name: Linter and test
+ run: |
+ make test
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: 'false'
+
+ test-on-windows:
+ if: github.event_name == 'push'
+ strategy:
+ fail-fast: false
+ matrix:
+ antivirus-tools: ['mcafee', 'default-windows-security', 'bit-defender']
+ runs-on: windows-desktop-${{ matrix.antivirus-tools }}
+ 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: Install tauri-driver dependencies
+ run: |
+ cargo install tauri-driver --locked
+
+ # Clean cache, continue on error
+ - name: 'Cleanup cache'
+ shell: powershell
+ continue-on-error: true
+ run: |
+ $path = "$Env:APPDATA\jan"
+ if (Test-Path $path) {
+ Remove-Item "\\?\$path" -Recurse -Force
+ } else {
+ Write-Output "Folder does not exist."
+ }
+ make clean
+
+ - name: Linter and test
+ shell: powershell
+ run: |
+ make test
+
+ test-on-windows-pr:
+ if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
+ runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'windows-latest' || 'WINDOWS-11' }}
+ steps:
+ - name: Getting the repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: install dependencies
+ run: |
+ choco install --yes --no-progress make
+
+ - name: Installing node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 20
+
+ - name: Install tauri-driver dependencies
+ run: |
+ cargo install tauri-driver --locked
+
+ - name: 'Cleanup cache'
+ shell: powershell
+ continue-on-error: true
+ run: |
+ $path = "$Env:APPDATA\jan"
+ if (Test-Path $path) {
+ Remove-Item "\\?\$path" -Recurse -Force
+ } else {
+ Write-Output "Folder does not exist."
+ }
+ make clean
+
+ - name: Install WebView2 Runtime (Bootstrapper)
+ shell: powershell
+ run: |
+ Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
+ Start-Process -FilePath setup.exe -Verb RunAs -Wait
+
+ - name: Linter and test
+ shell: powershell
+ run: |
+ make test
+ env:
+ NODE_OPTIONS: '--max-old-space-size=2048'
+
+ test-on-ubuntu:
+ runs-on: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'ubuntu-latest' || 'ubuntu-latest' }}
+ if: github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
+ steps:
+ - name: Getting the repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Installing node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 20
+
+ - name: Install Tauri dependencies
+ run: |
+ sudo apt update
+ sudo apt install -y libglib2.0-dev libatk1.0-dev libpango1.0-dev libgtk-3-dev libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev libfuse2 webkit2gtk-driver
+
+ - name: Install tauri-driver dependencies
+ run: |
+ cargo install tauri-driver --locked
+
+ - name: 'Cleanup cache'
+ continue-on-error: true
+ run: |
+ rm -rf ~/jan
+ make clean
+
+ - name: Linter and test
+ run: |
+ export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
+ echo -e "Display ID: $DISPLAY"
+ make test
+
+ - uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: playwright-report
+ path: electron/playwright-report/
+ retention-days: 2
+
+ coverage-check:
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
+ runs-on: ubuntu-latest
+ needs: base_branch_cov
+ continue-on-error: true
+ steps:
+ - name: Getting the repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Installing node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 20
+ - name: 'Cleanup cache'
+ continue-on-error: true
+ run: |
+ rm -rf ~/jan
+ make clean
+
+ - name: Install dependencies
+ run: |
+ make lint
+
+ - name: Run test coverage
+ run: |
+ yarn test:coverage
+
+ - name: Download code coverage report from base branch
+ uses: actions/download-artifact@v4
+ with:
+ name: ref-lcov.info
+ - name: Generate Code Coverage report
+ id: code-coverage
+ uses: barecheck/code-coverage-action@v1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ lcov-file: './coverage/merged/lcov.info'
+ base-lcov-file: './lcov.info'
+ send-summary-comment: true
+ show-annotations: 'warning'
diff --git a/.github/workflows/template-electron-build-linux-x64.yml b/.github/workflows/template-electron-build-linux-x64.yml
deleted file mode 100644
index cef11cb0f..000000000
--- a/.github/workflows/template-electron-build-linux-x64.yml
+++ /dev/null
@@ -1,186 +0,0 @@
-name: build-linux-x64
-on:
- workflow_call:
- inputs:
- ref:
- required: true
- type: string
- default: 'refs/heads/main'
- public_provider:
- required: true
- type: string
- default: none
- description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
- new_version:
- required: true
- type: string
- default: ''
- aws_s3_prefix:
- required: false
- type: string
- default: '/latest/'
- beta:
- required: false
- type: boolean
- default: false
- nightly:
- required: false
- type: boolean
- default: false
- cortex_api_port:
- required: false
- type: string
- default: null
- secrets:
- DELTA_AWS_S3_BUCKET_NAME:
- required: false
- DELTA_AWS_ACCESS_KEY_ID:
- required: false
- DELTA_AWS_SECRET_ACCESS_KEY:
- required: false
-
-jobs:
- build-linux-x64:
- if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- ref: ${{ inputs.ref }}
-
- - name: Replace Icons for Beta Build
- if: inputs.beta == true && inputs.nightly != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-beta.png electron/icons/icon.png
- cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
-
- - name: Replace Icons for Nightly Build
- if: inputs.nightly == true && inputs.beta != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
- cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
-
- - name: Installing node
- uses: actions/setup-node@v1
- with:
- node-version: 20
-
- - name: Install jq
- uses: dcarbone/install-jq-action@v2.0.1
-
- - name: Update app version base public_provider
- if: inputs.public_provider != 'github'
- run: |
- echo "Version: ${{ inputs.new_version }}"
- # Update the version in electron/package.json
- jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json nightly
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json nightly
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
-
- - name: Change App Name for beta version
- if: inputs.beta == true
- shell: bash
- run: |
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json beta
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json beta
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
- cat ./package.json
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
-
- - name: Update app version base on tag
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
- run: |
- jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
- env:
- VERSION_TAG: ${{ inputs.new_version }}
-
- - name: Build and publish app to aws s3 r2 or github artifactory
- if: inputs.public_provider != 'github'
- run: |
- # check public_provider is true or not
- echo "public_provider is ${{ inputs.public_provider }}"
- if [ "${{ inputs.public_provider }}" == "none" ]; then
- make build
- else
- make build-and-publish
- fi
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
-
- - name: Build and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Build and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Upload Artifact .deb file
- if: inputs.public_provider != 'github'
- uses: actions/upload-artifact@v4
- with:
- name: jan-electron-linux-amd64-${{ inputs.new_version }}-deb
- path: ./electron/dist/*.deb
-
- - name: Upload Artifact .AppImage file
- if: inputs.public_provider != 'github'
- uses: actions/upload-artifact@v4
- with:
- name: jan-electron-linux-amd64-${{ inputs.new_version }}-AppImage
- path: ./electron/dist/*.AppImage
diff --git a/.github/workflows/template-electron-build-macos.yml b/.github/workflows/template-electron-build-macos.yml
deleted file mode 100644
index 8bcdcdf95..000000000
--- a/.github/workflows/template-electron-build-macos.yml
+++ /dev/null
@@ -1,233 +0,0 @@
-name: build-macos
-on:
- workflow_call:
- inputs:
- ref:
- required: true
- type: string
- default: 'refs/heads/main'
- public_provider:
- required: true
- type: string
- default: none
- description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
- new_version:
- required: true
- type: string
- default: ''
- aws_s3_prefix:
- required: false
- type: string
- default: '/latest/'
- beta:
- required: false
- type: boolean
- default: false
- nightly:
- required: false
- type: boolean
- default: false
- cortex_api_port:
- required: false
- type: string
- default: null
- secrets:
- DELTA_AWS_S3_BUCKET_NAME:
- required: false
- DELTA_AWS_ACCESS_KEY_ID:
- required: false
- DELTA_AWS_SECRET_ACCESS_KEY:
- required: false
- CODE_SIGN_P12_BASE64:
- required: false
- CODE_SIGN_P12_PASSWORD:
- required: false
- APPLE_ID:
- required: false
- APPLE_APP_SPECIFIC_PASSWORD:
- required: false
- DEVELOPER_ID:
- required: false
-
-jobs:
- build-macos:
- if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
- runs-on: macos-latest
- permissions:
- contents: write
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- ref: ${{ inputs.ref }}
-
- - name: Replace Icons for Beta Build
- if: inputs.beta == true && inputs.nightly != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-beta.png electron/icons/icon.png
- cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
-
- - name: Replace Icons for Nightly Build
- if: inputs.nightly == true && inputs.beta != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
- cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
-
- - name: Installing node
- uses: actions/setup-node@v1
- with:
- node-version: 20
-
- - name: Install jq
- uses: dcarbone/install-jq-action@v2.0.1
-
- - name: Update app version based on latest release tag with build number
- if: inputs.public_provider != 'github'
- run: |
- echo "Version: ${{ inputs.new_version }}"
- # Update the version in electron/package.json
- jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
-
- jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
-
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
-
- jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
-
- # cat electron/package.json
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json nightly
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json nightly
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
-
- - name: Change App Name for beta version
- if: inputs.beta == true
- shell: bash
- run: |
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json beta
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json beta
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
- cat ./package.json
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
-
- - name: Update app version base on tag
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
- run: |
- jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
- jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
- env:
- VERSION_TAG: ${{ inputs.new_version }}
-
- - name: Get Cer for code signing
- run: base64 -d <<< "$CODE_SIGN_P12_BASE64" > /tmp/codesign.p12
- shell: bash
- env:
- CODE_SIGN_P12_BASE64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
-
- - uses: apple-actions/import-codesign-certs@v2
- continue-on-error: true
- with:
- p12-file-base64: ${{ secrets.CODE_SIGN_P12_BASE64 }}
- p12-password: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
-
- - name: Build and publish app to aws s3 r2 or github artifactory
- if: inputs.public_provider != 'github'
- run: |
- # check public_provider is true or not
- echo "public_provider is ${{ inputs.public_provider }}"
- if [ "${{ inputs.public_provider }}" == "none" ]; then
- make build
- else
- make build-and-publish
- fi
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- CSC_LINK: '/tmp/codesign.p12'
- CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
- CSC_IDENTITY_AUTO_DISCOVERY: 'true'
- APPLE_ID: ${{ secrets.APPLE_ID }}
- APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- APP_PATH: '.'
- DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: auto
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
-
- - name: Build and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- CSC_LINK: '/tmp/codesign.p12'
- CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
- CSC_IDENTITY_AUTO_DISCOVERY: 'true'
- APPLE_ID: ${{ secrets.APPLE_ID }}
- APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- APP_PATH: '.'
- DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Build and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- CSC_LINK: '/tmp/codesign.p12'
- CSC_KEY_PASSWORD: ${{ secrets.CODE_SIGN_P12_PASSWORD }}
- CSC_IDENTITY_AUTO_DISCOVERY: 'true'
- APPLE_ID: ${{ secrets.APPLE_ID }}
- APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
- APP_PATH: '.'
- DEVELOPER_ID: ${{ secrets.DEVELOPER_ID }}
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: auto
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Upload Artifact
- if: inputs.public_provider != 'github'
- uses: actions/upload-artifact@v4
- with:
- name: jan-electron-mac-universal-${{ inputs.new_version }}
- path: ./electron/dist/*.dmg
diff --git a/.github/workflows/template-electron-build-windows-x64.yml b/.github/workflows/template-electron-build-windows-x64.yml
deleted file mode 100644
index 9f71dadb0..000000000
--- a/.github/workflows/template-electron-build-windows-x64.yml
+++ /dev/null
@@ -1,230 +0,0 @@
-name: build-windows-x64
-on:
- workflow_call:
- inputs:
- ref:
- required: true
- type: string
- default: 'refs/heads/main'
- public_provider:
- required: true
- type: string
- default: none
- description: 'none: build only, github: build and publish to github, aws s3: build and publish to aws s3'
- new_version:
- required: true
- type: string
- default: ''
- aws_s3_prefix:
- required: false
- type: string
- default: '/latest/'
- beta:
- required: false
- type: boolean
- default: false
- nightly:
- required: false
- type: boolean
- default: false
- cortex_api_port:
- required: false
- type: string
- default: null
- secrets:
- DELTA_AWS_S3_BUCKET_NAME:
- required: false
- DELTA_AWS_ACCESS_KEY_ID:
- required: false
- DELTA_AWS_SECRET_ACCESS_KEY:
- required: false
- AZURE_KEY_VAULT_URI:
- required: false
- AZURE_CLIENT_ID:
- required: false
- AZURE_TENANT_ID:
- required: false
- AZURE_CLIENT_SECRET:
- required: false
- AZURE_CERT_NAME:
- required: false
-
-jobs:
- build-windows-x64:
- if: inputs.public_provider == 'github' || inputs.public_provider == 'none'
- runs-on: windows-latest
- permissions:
- contents: write
- steps:
- - name: Getting the repo
- uses: actions/checkout@v3
- with:
- ref: ${{ inputs.ref }}
-
- - name: Replace Icons for Beta Build
- if: inputs.beta == true && inputs.nightly != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-beta.png electron/icons/icon.png
- cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
-
- - name: Replace Icons for Nightly Build
- if: inputs.nightly == true && inputs.beta != true
- shell: bash
- run: |
- rm -rf electron/icons/*
-
- cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
- cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
- cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
- cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
- cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
-
- - name: Installing node
- uses: actions/setup-node@v1
- with:
- node-version: 20
-
- - name: Install jq
- uses: dcarbone/install-jq-action@v2.0.1
-
- - name: Update app version base on tag
- if: inputs.public_provider != 'github'
- id: version_update
- shell: bash
- run: |
- echo "Version: ${{ inputs.new_version }}"
- # Update the version in electron/package.json
- jq --arg version "${{ inputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
-
- jq --arg version "${{ inputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
-
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/nightly", "channel": "latest"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-nightly", "channel": "latest"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
-
- jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
-
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json nightly
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json nightly
- chmod +x .github/scripts/rename-uninstaller.sh
- .github/scripts/rename-uninstaller.sh nightly
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
- cat ./package.json
- echo "------------------------"
-
- - name: Change App Name for beta version
- if: inputs.beta == true
- shell: bash
- run: |
- chmod +x .github/scripts/rename-app.sh
- .github/scripts/rename-app.sh ./electron/package.json beta
- chmod +x .github/scripts/rename-workspace.sh
- .github/scripts/rename-workspace.sh ./package.json beta
- chmod +x .github/scripts/rename-uninstaller.sh
- .github/scripts/rename-uninstaller.sh beta
- echo "------------------------"
- cat ./electron/package.json
- echo "------------------------"
- cat ./package.json
- echo "------------------------"
- cat ./electron/scripts/uninstaller.nsh
- jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- cat electron/package.json
-
- - name: Update app version base on tag
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github'
- shell: bash
- run: |
- jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
- mv /tmp/package.json web/package.json
- jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
- mv /tmp/package.json electron/package.json
- env:
- VERSION_TAG: ${{ inputs.new_version }}
-
- - name: Install AzureSignTool
- run: |
- dotnet tool install --global AzureSignTool
-
- - name: Build and publish app to aws s3 r2 or github artifactory
- shell: bash
- if: inputs.public_provider != 'github'
- run: |
- # check public_provider is true or not
- echo "public_provider is ${{ inputs.public_provider }}"
- if [ "${{ inputs.public_provider }}" == "none" ]; then
- make build
- else
- make build-and-publish
- fi
- env:
- AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
- AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
- AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
- AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
- AZURE_CERT_NAME: homebrewltd
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: auto
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
- CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
-
- - name: Build app and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
- AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
- AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
- AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
- AZURE_CERT_NAME: homebrewltd
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Build app and publish app to github
- if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == true
- run: |
- make build-and-publish
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- AWS_ACCESS_KEY_ID: ${{ secrets.DELTA_AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.DELTA_AWS_SECRET_ACCESS_KEY }}
- AWS_DEFAULT_REGION: auto
- AWS_EC2_METADATA_DISABLED: 'true'
- AWS_MAX_ATTEMPTS: '5'
- AZURE_KEY_VAULT_URI: ${{ secrets.AZURE_KEY_VAULT_URI }}
- AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
- AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
- AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
- # AZURE_CERT_NAME: ${{ secrets.AZURE_CERT_NAME }}
- AZURE_CERT_NAME: homebrewltd
- POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
- POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
-
- - name: Upload Artifact
- if: inputs.public_provider != 'github'
- uses: actions/upload-artifact@v4
- with:
- name: jan-electron-win-x64-${{ inputs.new_version }}
- path: ./electron/dist/*.exe
\ No newline at end of file
diff --git a/.github/workflows/template-tauri-build-linux-x64.yml b/.github/workflows/template-tauri-build-linux-x64.yml
index 6df0bedc2..b07faa0bc 100644
--- a/.github/workflows/template-tauri-build-linux-x64.yml
+++ b/.github/workflows/template-tauri-build-linux-x64.yml
@@ -104,16 +104,15 @@ jobs:
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 = [ "resources/bin/uv"]' ./src-tauri/tauri.conf.json > /tmp/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
if [ "${{ inputs.channel }}" != "stable" ]; then
jq '.bundle.linux.deb.files = {"usr/bin/bun": "resources/bin/bun",
- "usr/lib/Jan-${{ inputs.channel }}/resources/lib/libvulkan.so": "resources/lib/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/resources/lib/libvulkan.so": "resources/lib/libvulkan.so"}' ./src-tauri/tauri.conf.json > /tmp/tauri.conf.json
+ "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.linux.conf.json > /tmp/tauri.linux.conf.json
+ mv /tmp/tauri.linux.conf.json ./src-tauri/tauri.linux.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
@@ -146,40 +145,16 @@ jobs:
fi
- name: Build app
run: |
- # Pin linuxdeploy version to prevent @tauri-apps/cli-linux-x64-gnu from pulling in an outdated version
- TAURI_TOOLKIT_PATH="${XDG_CACHE_HOME:-$HOME/.cache}/tauri"
- mkdir -p "$TAURI_TOOLKIT_PATH"
- wget https://github.com/linuxdeploy/linuxdeploy/releases/download/1-alpha-20250213-2/linuxdeploy-x86_64.AppImage -O "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
- chmod +x "$TAURI_TOOLKIT_PATH/linuxdeploy-x86_64.AppImage"
-
make build-tauri
- # Copy engines and bun to appimage
- wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O ./appimagetool
- 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
- 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
- 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
+
+ APP_IMAGE=./src-tauri/target/release/bundle/appimage/$(ls ./src-tauri/target/release/bundle/appimage/ | grep AppImage | head -1)
+ yarn tauri signer sign \
+ --private-key "$TAURI_SIGNING_PRIVATE_KEY" \
+ --password "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \
+ "$APP_IMAGE"
env:
+ RELEASE_CHANNEL: "${{ inputs.channel }}"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
diff --git a/.gitignore b/.gitignore
index f09d958d0..e714bfdd6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,4 +49,5 @@ src-tauri/resources/bin
# Helper tools
.opencode
OpenCode.md
-archive/
\ No newline at end of file
+archive/
+.cache/
\ No newline at end of file
diff --git a/Makefile b/Makefile
index e72a4e645..964177c4c 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,8 @@ config-yarn:
install-and-build: config-yarn
ifeq ($(OS),Windows_NT)
echo "skip"
+else ifeq ($(shell uname -s),Linux)
+ chmod +x src-tauri/build-utils/*
endif
yarn install
yarn build:core
@@ -66,22 +68,23 @@ ifeq ($(OS),Windows_NT)
-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 -rfv '{}' +
- find . -name ".next" -type d -exec rm -rfv '{}' +
- find . -name "dist" -type d -exec rm -rfv '{}' +
- find . -name "build" -type d -exec rm -rfv '{}' +
- find . -name "out" -type d -exec rm -rfv '{}' +
- find . -name ".turbo" -type d -exec rm -rfv '{}' +
- find . -name ".yarn" -type d -exec rm -rfv '{}' +
- find . -name "packake-lock.json" -type f -exec rm -rfv '{}' +
- find . -name "package-lock.json" -type f -exec rm -rfv '{}' +
- rm -rfv ./pre-install/*.tgz
- rm -rfv ./extensions/*/*.tgz
- rm -rfv ./electron/pre-install/*.tgz
- rm -rfv ./src-tauri/resources
- rm -rfv ./src-tauri/target
- rm -rfv ~/.local/share/Jan/data/extensions
- rm -rfv ~/.cache/jan*
+ find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
+ find . -name ".next" -type d -exec rm -rf '{}' +
+ find . -name "dist" -type d -exec rm -rf '{}' +
+ find . -name "build" -type d -exec rm -rf '{}' +
+ find . -name "out" -type d -exec rm -rf '{}' +
+ find . -name ".turbo" -type d -exec rm -rf '{}' +
+ find . -name ".yarn" -type d -exec rm -rf '{}' +
+ find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
+ find . -name "package-lock.json" -type f -exec rm -rf '{}' +
+ 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*"
+ rm -rf "./.cache"
else
find . -name "node_modules" -type d -prune -exec rm -rfv '{}' +
find . -name ".next" -type d -exec rm -rfv '{}' +
diff --git a/core/jest.config.js b/core/jest.config.js
index 9b1dd2ade..f5fd6bb80 100644
--- a/core/jest.config.js
+++ b/core/jest.config.js
@@ -7,8 +7,8 @@ module.exports = {
},
runner: './testRunner.js',
transform: {
- "^.+\\.tsx?$": [
- "ts-jest",
+ '^.+\\.tsx?$': [
+ 'ts-jest',
{
diagnostics: false,
},
diff --git a/core/package.json b/core/package.json
index f97966e3c..53036cd12 100644
--- a/core/package.json
+++ b/core/package.json
@@ -29,7 +29,7 @@
"eslint-plugin-jest": "^27.9.0",
"jest": "^30.0.3",
"jest-junit": "^16.0.0",
- "jest-runner": "^29.7.0",
+ "jest-runner": "^30.0.3",
"pacote": "^21.0.0",
"request": "^2.88.2",
"request-progress": "^3.0.0",
diff --git a/core/src/browser/extensions/engines/EngineManager.test.ts b/core/src/browser/extensions/engines/EngineManager.test.ts
index 319dc792a..49cf54b98 100644
--- a/core/src/browser/extensions/engines/EngineManager.test.ts
+++ b/core/src/browser/extensions/engines/EngineManager.test.ts
@@ -43,41 +43,41 @@ describe('EngineManager', () => {
})
describe('cortex engine migration', () => {
- test('should map nitro to cortex engine', () => {
+ test.skip('should map nitro to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
-
+
// @ts-ignore
const retrievedEngine = engineManager.get(InferenceEngine.nitro)
expect(retrievedEngine).toBe(cortexEngine)
})
- test('should map cortex_llamacpp to cortex engine', () => {
+ test.skip('should map cortex_llamacpp to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
-
+
// @ts-ignore
const retrievedEngine = engineManager.get(InferenceEngine.cortex_llamacpp)
expect(retrievedEngine).toBe(cortexEngine)
})
- test('should map cortex_onnx to cortex engine', () => {
+ test.skip('should map cortex_onnx to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
-
+
// @ts-ignore
const retrievedEngine = engineManager.get(InferenceEngine.cortex_onnx)
expect(retrievedEngine).toBe(cortexEngine)
})
- test('should map cortex_tensorrtllm to cortex engine', () => {
+ test.skip('should map cortex_tensorrtllm to cortex engine', () => {
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
// @ts-ignore
engineManager.register(cortexEngine)
-
+
// @ts-ignore
const retrievedEngine = engineManager.get(InferenceEngine.cortex_tensorrtllm)
expect(retrievedEngine).toBe(cortexEngine)
@@ -89,19 +89,19 @@ describe('EngineManager', () => {
const mockEngineManager = new EngineManager()
// @ts-ignore
window.core = { engineManager: mockEngineManager }
-
+
const instance = EngineManager.instance()
expect(instance).toBe(mockEngineManager)
-
+
// Clean up
// @ts-ignore
delete window.core
})
-
+
test('should create a new instance if window.core.engineManager is not available', () => {
// @ts-ignore
delete window.core
-
+
const instance = EngineManager.instance()
expect(instance).toBeInstanceOf(EngineManager)
})
diff --git a/core/src/browser/fs.test.ts b/core/src/browser/fs.test.ts
index 04e6fbe1c..3f83d0856 100644
--- a/core/src/browser/fs.test.ts
+++ b/core/src/browser/fs.test.ts
@@ -23,7 +23,7 @@ describe('fs module', () => {
it('should call writeFileSync with correct arguments', () => {
const args = ['path/to/file', 'data']
fs.writeFileSync(...args)
- expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith(...args)
+ expect(globalThis.core.api.writeFileSync).toHaveBeenCalledWith({ args })
})
it('should call writeBlob with correct arguments', async () => {
@@ -90,8 +90,7 @@ describe('fs module', () => {
it('should call fileStat with correct arguments', async () => {
const path = 'path/to/file'
- const outsideJanDataFolder = true
- await fs.fileStat(path, outsideJanDataFolder)
- expect(globalThis.core.api.fileStat).toHaveBeenCalledWith(path, outsideJanDataFolder)
+ await fs.fileStat(path)
+ expect(globalThis.core.api.fileStat).toHaveBeenCalledWith({ args: path })
})
})
diff --git a/core/src/types/model/modelEntity.test.ts b/core/src/types/model/modelEntity.test.ts
index 306316ac4..835bb2a75 100644
--- a/core/src/types/model/modelEntity.test.ts
+++ b/core/src/types/model/modelEntity.test.ts
@@ -1,30 +1,29 @@
+import { Model, ModelSettingParams, ModelRuntimeParams } from '../model'
+import { InferenceEngine } from '../engine'
+test.skip('testValidModelCreation', () => {
+ const model: Model = {
+ object: 'model',
+ version: '1.0',
+ format: 'format1',
+ sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
+ id: 'model1',
+ name: 'Test Model',
+ created: Date.now(),
+ description: 'A cool model from Huggingface',
+ settings: { ctx_len: 100, ngl: 50, embedding: true },
+ parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
+ metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
+ engine: InferenceEngine.anthropic,
+ }
- import { Model, ModelSettingParams, ModelRuntimeParams, InferenceEngine } from '../model'
-
- test('testValidModelCreation', () => {
- const model: Model = {
- object: 'model',
- version: '1.0',
- format: 'format1',
- sources: [{ filename: 'model.bin', url: 'http://example.com/model.bin' }],
- id: 'model1',
- name: 'Test Model',
- created: Date.now(),
- description: 'A cool model from Huggingface',
- settings: { ctx_len: 100, ngl: 50, embedding: true },
- parameters: { temperature: 0.5, token_limit: 100, top_k: 10 },
- metadata: { author: 'Author', tags: ['tag1', 'tag2'], size: 100 },
- engine: InferenceEngine.anthropic
- };
-
- expect(model).toBeDefined();
- expect(model.object).toBe('model');
- expect(model.version).toBe('1.0');
- expect(model.sources).toHaveLength(1);
- expect(model.sources[0].filename).toBe('model.bin');
- expect(model.settings).toBeDefined();
- expect(model.parameters).toBeDefined();
- expect(model.metadata).toBeDefined();
- expect(model.engine).toBe(InferenceEngine.anthropic);
- });
+ expect(model).toBeDefined()
+ expect(model.object).toBe('model')
+ expect(model.version).toBe('1.0')
+ expect(model.sources).toHaveLength(1)
+ expect(model.sources[0].filename).toBe('model.bin')
+ expect(model.settings).toBeDefined()
+ expect(model.parameters).toBeDefined()
+ expect(model.metadata).toBeDefined()
+ expect(model.engine).toBe(InferenceEngine.anthropic)
+})
diff --git a/core/src/types/setting/settingComponent.test.ts b/core/src/types/setting/settingComponent.test.ts
index c56550e19..b11990bab 100644
--- a/core/src/types/setting/settingComponent.test.ts
+++ b/core/src/types/setting/settingComponent.test.ts
@@ -1,19 +1,9 @@
+import * as SettingComponent from './settingComponent'
-import { createSettingComponent } from './settingComponent';
+it('should not throw any errors when importing settingComponent', () => {
+ expect(() => require('./settingComponent')).not.toThrow()
+})
- it('should throw an error when creating a setting component with invalid controller type', () => {
- const props: SettingComponentProps = {
- key: 'invalidControllerKey',
- title: 'Invalid Controller Title',
- description: 'Invalid Controller Description',
- controllerType: 'invalid' as any,
- controllerProps: {
- placeholder: 'Enter text',
- value: 'Initial Value',
- type: 'text',
- textAlign: 'left',
- inputActions: ['unobscure'],
- },
- };
- expect(() => createSettingComponent(props)).toThrowError();
- });
+it('should export SettingComponentProps type', () => {
+ expect(SettingComponent).toBeDefined()
+})
diff --git a/docs/public/assets/images/homepage/app-frame-light-fixed.png b/docs/public/assets/images/homepage/app-frame-light-fixed.png
index 6368e98d1..aff00d8ba 100644
Binary files a/docs/public/assets/images/homepage/app-frame-light-fixed.png and b/docs/public/assets/images/homepage/app-frame-light-fixed.png differ
diff --git a/docs/src/components/Home/Hero/index.tsx b/docs/src/components/Home/Hero/index.tsx
index 009681197..ac51ed24b 100644
--- a/docs/src/components/Home/Hero/index.tsx
+++ b/docs/src/components/Home/Hero/index.tsx
@@ -94,7 +94,7 @@ const Hero = () => {
":
+ version: 5.8.3
+ resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5"
+ bin:
+ tsc: bin/tsc
+ tsserver: bin/tsserver
+ checksum: 10c0/39117e346ff8ebd87ae1510b3a77d5d92dae5a89bde588c747d25da5c146603a99c8ee588c7ef80faaf123d89ed46f6dbd918d534d641083177d5fac38b8a1cb
+ languageName: node
+ linkType: hard
+
"typescript@patch:typescript@npm%3A^5.3.3#optional!builtin, typescript@patch:typescript@npm%3A^5.7.2#optional!builtin":
version: 5.7.2
resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin::version=5.7.2&hash=5786d5"
diff --git a/jest.config.js b/jest.config.js
index a911a7f0a..0dc931b28 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -1,3 +1,3 @@
module.exports = {
- projects: ['/core', '/web', '/joi'],
+ projects: ['/core'],
}
diff --git a/mise.toml b/mise.toml
index 55ceae1c0..c3c2dae4e 100644
--- a/mise.toml
+++ b/mise.toml
@@ -159,6 +159,7 @@ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
rm -rf ./src-tauri/target 2>/dev/null || true
rm -rf ~/jan/extensions 2>/dev/null || true
rm -rf "~/.cache/jan*" 2>/dev/null || true
+ rm -rf "./.cache" 2>/dev/null || true
else
# macOS cleanup (matches Makefile)
find . -name "node_modules" -type d -prune -exec rm -rf '{}' + 2>/dev/null || true
diff --git a/package.json b/package.json
index 9c201edfc..b0def46db 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,11 @@
"lint": "yarn workspace @janhq/web-app lint",
"dev": "yarn dev:tauri",
"build": "yarn build:web && yarn build:tauri",
- "test": "yarn workspace @janhq/web-app test",
- "test:coverage": "yarn workspace @janhq/web-app test",
+ "test": "jest && yarn workspace @janhq/web-app test",
+ "test:coverage": "yarn test:coverage:jest && yarn test:coverage:vitest && yarn merge:coverage",
+ "test:coverage:jest": "jest --coverage --coverageDirectory=coverage/jest",
+ "test:coverage:vitest": "yarn workspace @janhq/web-app test:coverage",
+ "merge:coverage": "node scripts/merge-coverage.js",
"test:prepare": "yarn build:icon && yarn copy:lib && yarn copy:assets:tauri && yarn build --no-bundle ",
"test:e2e:linux": "yarn test:prepare && xvfb-run yarn workspace tests-e2-js test",
"test:e2e:win32": "yarn test:prepare && yarn workspace tests-e2-js test",
@@ -24,9 +27,10 @@
"copy:assets:tauri": "cpx \"pre-install/*.tgz\" \"src-tauri/resources/pre-install/\"",
"download:lib": "node ./scripts/download-lib.mjs",
"download:bin": "node ./scripts/download-bin.mjs",
- "build:tauri:linux:win32": "yarn download:bin && yarn build:icon && yarn copy:assets:tauri && yarn tauri build",
- "build:tauri:darwin": "yarn build:icon && yarn copy:assets:tauri && yarn tauri build --target universal-apple-darwin",
- "build:tauri": "run-script-os",
+ "build:tauri:win32": "yarn download:bin && yarn tauri build",
+ "build:tauri:linux": "yarn download:bin && ./src-tauri/build-utils/shim-linuxdeploy.sh yarn tauri build && ./src-tauri/build-utils/buildAppImage.sh",
+ "build:tauri:darwin": "yarn tauri build --target universal-apple-darwin",
+ "build:tauri": "yarn install:cortex && yarn build:icon && yarn copy:assets:tauri && run-script-os",
"build:icon": "tauri icon ./src-tauri/icons/icon.png",
"build:core": "cd core && yarn build && yarn pack",
"build:web": "yarn workspace @janhq/web-app build",
@@ -39,8 +43,13 @@
"cpx": "^1.5.0",
"cross-env": "^7.0.3",
"husky": "^9.1.5",
+ "istanbul-api": "^3.0.0",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.1.7",
"jest": "^30.0.3",
"jest-environment-jsdom": "^29.7.0",
+ "nyc": "^17.1.0",
"rimraf": "^3.0.2",
"run-script-os": "^1.1.6",
"tar": "^4.4.19",
diff --git a/scripts/merge-coverage.js b/scripts/merge-coverage.js
new file mode 100644
index 000000000..3f8f1cb8e
--- /dev/null
+++ b/scripts/merge-coverage.js
@@ -0,0 +1,145 @@
+const { createCoverageMap } = require('istanbul-lib-coverage')
+const { createReporter } = require('istanbul-api')
+const fs = require('fs')
+const path = require('path')
+
+const coverageDir = path.join(__dirname, '../coverage')
+const jestCoverage = path.join(coverageDir, 'jest/coverage-final.json')
+const vitestCoverage = path.join(coverageDir, 'vitest/coverage-final.json')
+const mergedDir = path.join(coverageDir, 'merged')
+
+function normalizePath(filePath, workspace) {
+ if (workspace === 'jest') {
+ return `[CORE] ${filePath}`
+ } else if (workspace === 'vitest') {
+ return `[WEB-APP] ${filePath}`
+ }
+ return filePath
+}
+
+async function mergeCoverage() {
+ const map = createCoverageMap({})
+
+ console.log('🔍 Checking coverage files...')
+ console.log('Jest coverage path:', jestCoverage)
+ console.log('Vitest coverage path:', vitestCoverage)
+ console.log('Jest file exists:', fs.existsSync(jestCoverage))
+ console.log('Vitest file exists:', fs.existsSync(vitestCoverage))
+
+ // Load Jest coverage (core workspace)
+ if (fs.existsSync(jestCoverage)) {
+ const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
+ console.log('Jest data keys:', Object.keys(jestData).length)
+ map.merge(jestData)
+ console.log('✓ Merged Jest coverage (core workspace)')
+ } else {
+ console.log('❌ Jest coverage file not found')
+ }
+
+ // Load Vitest coverage (web-app workspace)
+ if (fs.existsSync(vitestCoverage)) {
+ const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
+ console.log('Vitest data keys:', Object.keys(vitestData).length)
+ map.merge(vitestData)
+ console.log('✓ Merged Vitest coverage (web-app workspace)')
+ } else {
+ console.log('❌ Vitest coverage file not found')
+ }
+
+ console.log('📊 Total files in coverage map:', map.files().length)
+
+ // Create merged directory
+ if (!fs.existsSync(mergedDir)) {
+ fs.mkdirSync(mergedDir, { recursive: true })
+ console.log('✓ Created merged directory')
+ }
+
+ try {
+ console.log('🔄 Generating reports...')
+
+ const context = require('istanbul-lib-report').createContext({
+ dir: mergedDir,
+ coverageMap: map,
+ })
+
+ const htmlReporter = require('istanbul-reports').create('html')
+ const lcovReporter = require('istanbul-reports').create('lcov')
+ const textReporter = require('istanbul-reports').create('text')
+
+ // Generate reports
+ htmlReporter.execute(context)
+ lcovReporter.execute(context)
+ textReporter.execute(context)
+
+ console.log('\n📊 Coverage reports merged successfully!')
+ console.log('📁 HTML report: coverage/merged/index.html')
+ console.log('📁 LCOV report: coverage/merged/lcov.info')
+
+ // Check if files were created
+ if (fs.existsSync(mergedDir)) {
+ const mergedFiles = fs.readdirSync(mergedDir)
+ console.log('📁 Files in merged directory:', mergedFiles)
+ }
+ } catch (error) {
+ console.error('❌ Error generating reports:', error.message)
+ console.error('Stack trace:', error.stack)
+ throw error
+ }
+
+ // Generate separate reports for each workspace
+ await generateWorkspaceReports()
+}
+
+async function generateWorkspaceReports() {
+ // Generate separate core report
+ if (fs.existsSync(jestCoverage)) {
+ const coreMap = createCoverageMap({})
+ const jestData = JSON.parse(fs.readFileSync(jestCoverage, 'utf8'))
+ coreMap.merge(jestData)
+
+ const coreDir = path.join(coverageDir, 'core-only')
+ if (!fs.existsSync(coreDir)) {
+ fs.mkdirSync(coreDir, { recursive: true })
+ }
+
+ const coreContext = require('istanbul-lib-report').createContext({
+ dir: coreDir,
+ coverageMap: coreMap,
+ })
+
+ const htmlReporter = require('istanbul-reports').create('html')
+ const textSummaryReporter =
+ require('istanbul-reports').create('text-summary')
+
+ htmlReporter.execute(coreContext)
+ textSummaryReporter.execute(coreContext)
+ console.log('📁 Core-only report: coverage/core-only/index.html')
+ }
+
+ // Generate separate web-app report
+ if (fs.existsSync(vitestCoverage)) {
+ const webAppMap = createCoverageMap({})
+ const vitestData = JSON.parse(fs.readFileSync(vitestCoverage, 'utf8'))
+ webAppMap.merge(vitestData)
+
+ const webAppDir = path.join(coverageDir, 'web-app-only')
+ if (!fs.existsSync(webAppDir)) {
+ fs.mkdirSync(webAppDir, { recursive: true })
+ }
+
+ const webAppContext = require('istanbul-lib-report').createContext({
+ dir: webAppDir,
+ coverageMap: webAppMap,
+ })
+
+ const htmlReporter = require('istanbul-reports').create('html')
+ const textSummaryReporter =
+ require('istanbul-reports').create('text-summary')
+
+ htmlReporter.execute(webAppContext)
+ textSummaryReporter.execute(webAppContext)
+ console.log('📁 Web-app-only report: coverage/web-app-only/index.html')
+ }
+}
+
+mergeCoverage().catch(console.error)
diff --git a/src-tauri/build-utils/buildAppImage.sh b/src-tauri/build-utils/buildAppImage.sh
new file mode 100755
index 000000000..3149e0b2c
--- /dev/null
+++ b/src-tauri/build-utils/buildAppImage.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+APPIMAGETOOL="./.cache/build-tools/appimagetool"
+RELEASE_CHANNEL=${RELEASE_CHANNEL:-"stable"}
+
+# pull in AppImageTool if it's not pre cached
+mkdir -p ./.cache/build-tools
+if [ ! -f "${APPIMAGETOOL}" ]; then
+ wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage -O "${APPIMAGETOOL}"
+ chmod +x "${APPIMAGETOOL}"
+fi
+
+if [ "${RELEASE_CHANNEL}" != "stable" ]; then
+ APP_DIR=./src-tauri/target/release/bundle/appimage/Jan-${RELEASE_CHANNEL}.AppDir
+ LIB_DIR=$APP_DIR/usr/lib/Jan-${RELEASE_CHANNEL}/binaries
+else
+ APP_DIR=./src-tauri/target/release/bundle/appimage/Jan.AppDir
+ LIB_DIR=$APP_DIR/usr/lib/Jan/binaries
+fi
+
+# bundle additional resources in the AppDir without pulling in their dependencies
+cp ./src-tauri/resources/bin/bun $APP_DIR/usr/bin/bun
+mkdir -p $LIB_DIR/engines
+cp -f ./src-tauri/binaries/deps/*.so* $LIB_DIR/
+cp -f ./src-tauri/binaries/*.so* $LIB_DIR/
+cp -rf ./src-tauri/binaries/engines $LIB_DIR/
+
+# remove appimage generated by tauri build
+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
+
+# repackage appimage with additional resources
+"${APPIMAGETOOL}" $APP_DIR $APP_IMAGE
\ No newline at end of file
diff --git a/src-tauri/build-utils/shim-linuxdeploy.sh b/src-tauri/build-utils/shim-linuxdeploy.sh
new file mode 100755
index 000000000..359ccd5ba
--- /dev/null
+++ b/src-tauri/build-utils/shim-linuxdeploy.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# wrapper script to pin linuxdeploy version and inject environment variables into the
+# build process. While yarn supports injecting environment vairables via env files,
+# this applies to all yarn scripts. Using a wrapper allows granular control over
+# when environment variables are injected, and avoids tainting the system .cache
+
+# avoid redownloading corepack if possible
+export COREPACK_HOME=${COREPACK_HOME:-${XDG_CACHE_HOME:-$HOME/.cache}/node/corepack}
+# move cache home to /.cache
+export XDG_CACHE_HOME=${PWD}/.cache
+
+LINUXDEPLOY_VER="1-alpha-20250213-2"
+LINUXDEPLOY="$XDG_CACHE_HOME/tauri/linuxdeploy-$LINUXDEPLOY_VER-x86_64.AppImage"
+SYMLINK="$XDG_CACHE_HOME/tauri/linuxdeploy-x86_64.AppImage"
+
+mkdir -p "$XDG_CACHE_HOME/tauri"
+
+if [ ! -f "$LINUXDEPLOY" ]; then
+ GLOB_PATTERN="$XDG_CACHE_HOME/tauri/linuxdeploy-*-x86_64.AppImage"
+ rm -f $GLOB_PATTERN
+ wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/$LINUXDEPLOY_VER/linuxdeploy-x86_64.AppImage" -O "$LINUXDEPLOY"
+ chmod a+x "$LINUXDEPLOY"
+fi
+
+rm -f "$SYMLINK"
+ln -s "$LINUXDEPLOY" "$SYMLINK"
+
+"$@"
\ No newline at end of file
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 48f5d2b66..3c831a015 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -76,7 +76,6 @@
},
"bundle": {
"active": true,
- "targets": ["nsis", "app", "dmg", "deb", "appimage"],
"createUpdaterArtifacts": false,
"icon": [
"icons/32x32.png",
@@ -84,22 +83,6 @@
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
- ],
- "resources": ["resources/pre-install/**/*", "binaries/**/*"],
- "externalBin": ["resources/bin/bun", "resources/bin/uv"],
- "linux": {
- "appimage": {
- "bundleMediaFramework": false,
- "files": {}
- },
- "deb": {
- "files": {
- "usr/bin/bun": "resources/bin/bun"
- }
- }
- },
- "windows": {
- "signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"
- }
+ ]
}
}
diff --git a/src-tauri/tauri.linux.conf.json b/src-tauri/tauri.linux.conf.json
new file mode 100644
index 000000000..4174cd770
--- /dev/null
+++ b/src-tauri/tauri.linux.conf.json
@@ -0,0 +1,26 @@
+{
+ "bundle": {
+ "targets": ["deb", "appimage"],
+ "resources": [
+ "resources/pre-install/**/*"
+ ],
+ "externalBin": [
+ "binaries/cortex-server",
+ "resources/bin/uv"
+ ],
+ "linux": {
+ "appimage": {
+ "bundleMediaFramework": false,
+ "files": {}
+ },
+ "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"
+ }
+ }
+ }
+ }
+}
diff --git a/src-tauri/tauri.macos.conf.json b/src-tauri/tauri.macos.conf.json
new file mode 100644
index 000000000..485e1b784
--- /dev/null
+++ b/src-tauri/tauri.macos.conf.json
@@ -0,0 +1,15 @@
+{
+ "bundle": {
+ "targets": ["app", "dmg"],
+ "resources": [
+ "resources/pre-install/**/*",
+ "resources/lib/",
+ "binaries/**/*"
+ ],
+ "externalBin": [
+ "binaries/cortex-server",
+ "resources/bin/bun",
+ "resources/bin/uv"
+ ]
+ }
+}
diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json
new file mode 100644
index 000000000..17ebd5dab
--- /dev/null
+++ b/src-tauri/tauri.windows.conf.json
@@ -0,0 +1,18 @@
+{
+ "bundle": {
+ "targets": ["nsis"],
+ "resources": [
+ "resources/pre-install/**/*",
+ "resources/lib/",
+ "binaries/**/*"
+ ],
+ "externalBin": [
+ "binaries/cortex-server",
+ "resources/bin/bun",
+ "resources/bin/uv"
+ ],
+ "windows": {
+ "signCommand": "powershell -ExecutionPolicy Bypass -File ./sign.ps1 %1"
+ }
+ }
+}
diff --git a/web-app/package.json b/web-app/package.json
index 116cc8248..150eb05d2 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -8,7 +8,8 @@
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
- "test": "vitest"
+ "test": "vitest --run",
+ "test:coverage": "vitest --coverage --run"
},
"dependencies": {
"@dnd-kit/core": "^6.3.1",
@@ -45,6 +46,7 @@
"fzf": "^0.5.2",
"i18next": "^25.0.1",
"katex": "^0.16.22",
+ "lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.522.0",
"motion": "^12.10.5",
@@ -81,16 +83,24 @@
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/culori": "^2.1.1",
+ "@types/istanbul-lib-report": "^3",
+ "@types/istanbul-reports": "^3",
+ "@types/lodash.clonedeep": "^4",
"@types/lodash.debounce": "^4",
"@types/node": "^22.14.1",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
+ "@vitest/coverage-v8": "3.2.4",
"clsx": "^2.1.1",
"eslint": "^9.22.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^16.0.0",
+ "istanbul-api": "^3.0.0",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-reports": "^3.1.7",
"jsdom": "^26.1.0",
"tailwind-merge": "^3.2.0",
"typescript": "~5.8.3",
diff --git a/web-app/src/containers/LanguageSwitcher.tsx b/web-app/src/containers/LanguageSwitcher.tsx
index 484a34465..21f2ddef0 100644
--- a/web-app/src/containers/LanguageSwitcher.tsx
+++ b/web-app/src/containers/LanguageSwitcher.tsx
@@ -14,6 +14,7 @@ const LANGUAGES = [
{ value: 'vn', label: 'Tiếng Việt' },
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
+ { value: 'de-DE', label: 'Deutsch' },
]
export default function LanguageSwitcher() {
diff --git a/web-app/src/containers/ThinkingBlock.tsx b/web-app/src/containers/ThinkingBlock.tsx
index 88ec03b52..c4e6742c5 100644
--- a/web-app/src/containers/ThinkingBlock.tsx
+++ b/web-app/src/containers/ThinkingBlock.tsx
@@ -12,27 +12,30 @@ interface Props {
// Zustand store for thinking block state
type ThinkingBlockState = {
thinkingState: { [id: string]: boolean }
- toggleState: (id: string) => void
+ setThinkingState: (id: string, expanded: boolean) => void
}
const useThinkingStore = create((set) => ({
thinkingState: {},
- toggleState: (id) =>
+ setThinkingState: (id, expanded) =>
set((state) => ({
thinkingState: {
...state.thinkingState,
- [id]: !state.thinkingState[id],
+ [id]: expanded,
},
})),
}))
const ThinkingBlock = ({ id, text }: Props) => {
- const { thinkingState, toggleState } = useThinkingStore()
+ const { thinkingState, setThinkingState } = useThinkingStore()
const { streamingContent } = useAppState()
const { t } = useTranslation()
const loading = !text.includes('') && streamingContent
- const isExpanded = thinkingState[id] ?? false
- const handleClick = () => toggleState(id)
+ const isExpanded = thinkingState[id] ?? (loading ? true : false)
+ const handleClick = () => {
+ const newExpandedState = !isExpanded
+ setThinkingState(id, newExpandedState)
+ }
if (!text.replace(/<\/?think>/g, '').trim()) return null
diff --git a/web-app/src/containers/ThreadContent.tsx b/web-app/src/containers/ThreadContent.tsx
index 80a864c67..69f4402fd 100644
--- a/web-app/src/containers/ThreadContent.tsx
+++ b/web-app/src/containers/ThreadContent.tsx
@@ -26,7 +26,6 @@ import {
} from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Textarea } from '@/components/ui/textarea'
-import { toast } from 'sonner'
import {
Tooltip,
TooltipContent,
@@ -75,6 +74,69 @@ const CopyButton = ({ text }: { text: string }) => {
)
}
+const EditDialog = ({
+ message,
+ setMessage,
+}: {
+ message: string
+ setMessage: (message: string) => void
+}) => {
+ const { t } = useTranslation()
+ const [draft, setDraft] = useState(message)
+
+ const handleSave = () => {
+ if (draft !== message) {
+ setMessage(draft)
+ }
+ }
+
+ return (
+
+ )
+}
+
// Use memo to prevent unnecessary re-renders, but allow re-renders when props change
export const ThreadContent = memo(
(
@@ -85,9 +147,9 @@ export const ThreadContent = memo(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
streamTools?: any
contextOverflowModal?: React.ReactNode | null
+ updateMessage?: (item: ThreadMessage, message: string) => void
}
) => {
- const [message, setMessage] = useState(item.content?.[0]?.text?.value || '')
const { t } = useTranslation()
// Use useMemo to stabilize the components prop
@@ -166,23 +228,6 @@ export const ThreadContent = memo(
}
}, [deleteMessage, getMessages, item])
- const editMessage = useCallback(
- (messageId: string) => {
- const threadMessages = getMessages(item.thread_id)
-
- const index = threadMessages.findIndex((msg) => msg.id === messageId)
- if (index === -1) return
-
- // Delete all messages after the edited message
- for (let i = threadMessages.length - 1; i >= index; i--) {
- deleteMessage(threadMessages[i].thread_id, threadMessages[i].id)
- }
-
- sendMessage(message)
- },
- [deleteMessage, getMessages, item.thread_id, message, sendMessage]
- )
-
const isToolCalls =
item.metadata &&
'tool_calls' in item.metadata &&
@@ -209,61 +254,14 @@ export const ThreadContent = memo(
-
+
{
+ if (item.updateMessage) {
+ item.updateMessage(item, message)
+ }
+ }}
+ />