diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..45fd5d159 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +## Describe Your Changes + +- + +## Fixes Issues + +- + +## Self Checklist + +- [ ] Added relevant comments, esp in complex areas +- [ ] Updated docs (for bug fixes / features) +- [ ] Created issues for follow-up changes or refactoring needed diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml index 1db805d4e..b5ffa9a3b 100644 --- a/.github/workflows/jan-electron-build-nightly.yml +++ b/.github/workflows/jan-electron-build-nightly.yml @@ -15,6 +15,7 @@ jobs: uses: unfor19/install-aws-cli-action@v1 - name: Delete cloudflare-r2 folder using awscli s3api + if: github.ref == 'refs/heads/main' continue-on-error: true run: | # Get the list of objects in the 'latest' folder @@ -34,25 +35,18 @@ jobs: AWS_DEFAULT_REGION: auto AWS_EC2_METADATA_DISABLED: "true" - build-macos: - runs-on: macos-latest + # Job create Update app version based on latest release tag with build number and save to output + get-update-version: + runs-on: ubuntu-latest needs: delete-cloudflare-r2-folder environment: production - permissions: - contents: write + outputs: + new_version: ${{ steps.version_update.outputs.new_version }} steps: - - name: Getting the repo - uses: actions/checkout@v3 - - - 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 id: version_update run: | @@ -82,11 +76,35 @@ jobs: # Remove the 'v' and append the build number to the version NEW_VERSION="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}" echo "New version: $NEW_VERSION" - - # Update the version in electron/package.json - jq --arg version "$NEW_VERSION" '.version = $version' electron/package.json > /tmp/package.json - mv /tmp/package.json electron/package.json echo "::set-output name=new_version::$NEW_VERSION" + build-macos: + runs-on: macos-latest + needs: [delete-cloudflare-r2-folder, get-update-version] + environment: production + permissions: + contents: write + steps: + - name: Getting the repo + uses: actions/checkout@v3 + + - 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 + id: version_update + run: | + # Update the version in electron/package.json + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json + mv /tmp/package.json electron/package.json + + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json @@ -119,26 +137,27 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: jan-mac-x64-${{ steps.version_update.outputs.new_version }} - path: ./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg + name: jan-mac-x64-${{ needs.get-update-version.outputs.new_version }} + path: ./electron/dist/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: jan-mac-arm64-${{ steps.version_update.outputs.new_version }} - path: ./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg + name: jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }} + path: ./electron/dist/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg - name: put-object using awscli s3api + if: github.ref == 'refs/heads/main' continue-on-error: true run: | ls -al ./electron/dist - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.zip" --content-type "application/zip" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg" --content-type "application/octet-stream" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.zip" --content-type "application/zip" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.zip" --body "./electron/dist/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.zip" --content-type "application/zip" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg" --body "./electron/dist/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-mac.yml" --body "./electron/dist/latest-mac.yml" --content-type "text/yaml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} @@ -148,7 +167,7 @@ jobs: build-windows-x64: runs-on: windows-latest - needs: delete-cloudflare-r2-folder + needs: [delete-cloudflare-r2-folder, get-update-version] permissions: contents: write steps: @@ -167,38 +186,12 @@ jobs: id: version_update shell: bash run: | - # Function to get the latest release tag - get_latest_tag() { - local retries=0 - local max_retries=3 - local tag - while [ $retries -lt $max_retries ]; do - tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name) - if [ -n "$tag" ] && [ "$tag" != "null" ]; then - echo $tag - return - else - let retries++ - echo "Retrying... ($retries/$max_retries)" - sleep 2 - fi - done - echo "Failed to fetch latest tag after $max_retries attempts." - exit 1 - } - - # Get the latest release tag from GitHub API - LATEST_TAG=$(get_latest_tag) - - # Remove the 'v' and append the build number to the version - NEW_VERSION="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}" - echo "New version: $NEW_VERSION" - # Update the version in electron/package.json - jq --arg version "$NEW_VERSION" '.version = $version' electron/package.json > /tmp/package.json + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json - echo "::set-output name=new_version::$NEW_VERSION" + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json @@ -223,18 +216,19 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: jan-win-x64-${{ steps.version_update.outputs.new_version }} + name: jan-win-x64-${{ needs.get-update-version.outputs.new_version }} path: ./electron/dist/*.exe - name: put-object using awscli s3api + if: github.ref == 'refs/heads/main' shell: bash run: | ls -al ./electron/dist - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe.blockmap" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe.blockmap" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ steps.version_update.outputs.new_version }}.exe" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe.blockmap" --body "./electron/dist/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe.blockmap" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe" --body "./electron/dist/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe" aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest.yml" --body "./electron/dist/latest.yml" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest.yml" --body "./electron/dist/latest.yml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/latest.yml" --body "./electron/dist/latest.yml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} @@ -243,7 +237,7 @@ jobs: build-linux-x64: runs-on: ubuntu-latest - needs: delete-cloudflare-r2-folder + needs: [delete-cloudflare-r2-folder, get-update-version] environment: production env: SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }} @@ -264,37 +258,11 @@ jobs: - name: Update app version base on tag id: version_update run: | - # Function to get the latest release tag - get_latest_tag() { - local retries=0 - local max_retries=3 - local tag - while [ $retries -lt $max_retries ]; do - tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name) - if [ -n "$tag" ] && [ "$tag" != "null" ]; then - echo $tag - return - else - let retries++ - echo "Retrying... ($retries/$max_retries)" - sleep 2 - fi - done - echo "Failed to fetch latest tag after $max_retries attempts." - exit 1 - } - - # Get the latest release tag from GitHub API - LATEST_TAG=$(get_latest_tag) - - # Remove the 'v' and append the build number to the version - NEW_VERSION="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}" - echo "New version: $NEW_VERSION" - # Update the version in electron/package.json - jq --arg version "$NEW_VERSION" '.version = $version' electron/package.json > /tmp/package.json + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json - echo "::set-output name=new_version::$NEW_VERSION" + jq --arg version "${{ needs.get-update-version.outputs.new_version }}" '.version = $version' web/package.json > /tmp/package.json + mv /tmp/package.json web/package.json jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}]' electron/package.json > /tmp/package.json mv /tmp/package.json electron/package.json @@ -307,16 +275,17 @@ jobs: - name: Upload Artifact uses: actions/upload-artifact@v2 with: - name: jan-linux-amd64-${{ steps.version_update.outputs.new_version }} + name: jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }} path: ./electron/dist/*.deb - name: put-object using awscli s3api + if: github.ref == 'refs/heads/main' run: | ls -al ./electron/dist - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ steps.version_update.outputs.new_version }}.deb" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb" --content-type "application/octet-stream" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb" --body "./electron/dist/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb" --content-type "application/octet-stream" aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "latest/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" - aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ steps.version_update.outputs.new_version }}/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" + aws s3api put-object --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --key "${{ needs.get-update-version.outputs.new_version }}/latest-linux.yml" --body "./electron/dist/latest-linux.yml" --content-type "text/yaml" env: AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }} @@ -324,7 +293,7 @@ jobs: AWS_EC2_METADATA_DISABLED: "true" noti-discord-nightly-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, delete-cloudflare-r2-folder] + needs: [build-macos, build-windows-x64, build-linux-x64, delete-cloudflare-r2-folder, get-update-version] environment: production if: github.event_name == 'schedule' runs-on: ubuntu-latest @@ -335,16 +304,29 @@ jobs: fetch-depth: "0" token: ${{ secrets.PAT_SERVICE_ACCOUNT }} + - name: Set version to environment variable + run: | + echo "VERSION=${{ needs.get-update-version.outputs.new_version }}" >> $GITHUB_ENV + - name: Notify Discord uses: Ilshidur/action-discord@master with: - args: "Nightly build artifact: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}" + args: | + Jan App Nightly build artifact version {{ VERSION }}: + - Windows: https://delta.jan.ai/{{ VERSION }}/jan-win-x64-{{ VERSION }}.exe + - macOS Intel: https://delta.jan.ai/{{ VERSION }}/jan-mac-x64-{{ VERSION }}.dmg + - macOS Apple Silicon: https://delta.jan.ai/{{ VERSION }}/jan-mac-arm64-{{ VERSION }}.dmg + - Linux: https://delta.jan.ai/{{ VERSION }}/jan-linux-amd64-{{ VERSION }}.deb + - Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} - name: Update README.md with artifact URL run: | - sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md git config --global user.email "service@jan.ai" git config --global user.name "Service Account" git add README.md @@ -354,9 +336,9 @@ jobs: GITHUB_RUN_ID: ${{ github.run_id }} noti-discord-manual-and-update-url-readme: - needs: [build-macos, build-windows-x64, build-linux-x64, delete-cloudflare-r2-folder] + needs: [build-macos, build-windows-x64, build-linux-x64, delete-cloudflare-r2-folder, get-update-version] environment: production - if: github.event_name == 'workflow_dispatch' + if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - name: Checkout code @@ -364,11 +346,21 @@ jobs: with: fetch-depth: "0" token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - + + - name: Set version to environment variable + run: | + echo "VERSION=${{ needs.get-update-version.outputs.new_version }}" >> $GITHUB_ENV + - name: Notify Discord uses: Ilshidur/action-discord@master with: - args: "Manual build artifact: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}" + args: | + Jan App Manual build artifact version {{ VERSION }}: + - Windows: https://delta.jan.ai/{{ VERSION }}/jan-win-x64-{{ VERSION }}.exe + - macOS Intel: https://delta.jan.ai/{{ VERSION }}/jan-mac-x64-{{ VERSION }}.dmg + - macOS Apple Silicon: https://delta.jan.ai/{{ VERSION }}/jan-mac-arm64-{{ VERSION }}.dmg + - Linux: https://delta.jan.ai/{{ VERSION }}/jan-linux-amd64-{{ VERSION }}.deb + - Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }} env: DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} @@ -376,7 +368,10 @@ jobs: - name: Update README.md with artifact URL if: github.ref == 'refs/heads/main' run: | - sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md + sed -i "s|||" README.md git config --global user.email "service@jan.ai" git config --global user.name "Service Account" git add README.md diff --git a/.github/workflows/jan-electron-build.yml b/.github/workflows/jan-electron-build.yml index eba68fea1..abd0aaeda 100644 --- a/.github/workflows/jan-electron-build.yml +++ b/.github/workflows/jan-electron-build.yml @@ -60,6 +60,8 @@ jobs: fi 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: ${{ steps.tag.outputs.tag }} @@ -120,6 +122,8 @@ jobs: fi 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: @@ -203,6 +207,8 @@ jobs: fi 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: ${{ steps.tag.outputs.tag }} diff --git a/.gitignore b/.gitignore index ba5983d2e..dbf94335a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ build .DS_Store electron/renderer electron/models +electron/docs package-lock.json *.log diff --git a/Makefile b/Makefile index a61ae92bb..65e21897d 100644 --- a/Makefile +++ b/Makefile @@ -52,21 +52,17 @@ build: check-file-counts clean: ifeq ($(OS),Windows_NT) powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist -Recurse -Directory | Remove-Item -Recurse -Force" - rmdir /s /q "%USERPROFILE%\AppData\Roaming\jan" - rmdir /s /q "%USERPROFILE%\AppData\Roaming\jan-electron" - rmdir /s /q "%USERPROFILE%\AppData\Local\jan*" + rmdir /s /q "%USERPROFILE%\jan\extensions" else ifeq ($(shell uname -s),Linux) 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 '{}' + - rm -rf "~/.config/jan" - rm -rf "~/.config/jan-electron" + rm -rf "~/jan/extensions" rm -rf "~/.cache/jan*" else 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 '{}' + - rm -rf ~/Library/Application\ Support/jan - rm -rf ~/Library/Application\ Support/jan-electron + rm -rf ~/jan/extensions rm -rf ~/Library/Caches/jan* endif diff --git a/README.md b/README.md index 2726baacf..c70e7f2f5 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute ## Download - + - + - - - + + + + + @@ -91,10 +110,10 @@ _Realtime Video: Jan v0.4.3-nightly on a Mac M1, 16GB Sonoma 14_ - [Jan website](https://jan.ai/) - [Jan Github](https://github.com/janhq/jan) -- [User Guides](https://jan.ai/docs) -- [Developer docs](https://jan.ai/docs/extensions/) +- [User Guides](https://jan.ai/guides/) +- [Developer docs](https://jan.ai/developer/) - [API reference](https://jan.ai/api-reference/) -- [Specs](https://jan.ai/specs/) +- [Specs](https://jan.ai/docs/) #### Nitro @@ -111,18 +130,7 @@ As Jan is in development mode, you might get stuck on a broken build. To reset your installation: -1. **Remove Jan from your Applications folder and Cache folder** - - ```bash - make clean - ``` - - This will remove all build artifacts and cached files: - - - Delete Jan from your `/Applications` folder - - Clear Application cache in `/Users/$(whoami)/Library/Caches/jan` - -2. Use the following commands to remove any dangling backend processes: +1. Use the following commands to remove any dangling backend processes: ```sh ps aux | grep nitro @@ -134,6 +142,18 @@ To reset your installation: kill -9 ``` +2. **Remove Jan from your Applications folder and Cache folder** + + ```bash + make clean + ``` + + This will remove all build artifacts and cached files: + + - Delete Jan extension from your `~/jan/extensions` folder + - Delete all `node_modules` in current folder + - Clear Application cache in `~/Library/Caches/jan` + ## Contributing Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file @@ -148,19 +168,19 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi 1. **Clone the repository and prepare:** -```bash - git clone https://github.com/janhq/jan - cd jan - git checkout -b DESIRED_BRANCH -``` + ```bash + git clone https://github.com/janhq/jan + cd jan + git checkout -b DESIRED_BRANCH + ``` 2. **Run development and use Jan Desktop** - ``` - make dev - ``` + ```bash + make dev + ``` - This will start the development server and open the desktop app. +This will start the development server and open the desktop app. ### For production build @@ -172,25 +192,6 @@ make build This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder. -## Nightly Build - -Our nightly build process for this project is defined in [`.github/workflows/jan-electron-build-nightly.yml`](.github/workflows/jan-electron-build-nightly.yml) - -The nightly build is triggered at 2:00 AM UTC every day. - -Getting on Nightly: - -1. Join our Discord server [here](https://discord.gg/FTk2MvZwJH) and go to channel [github-jan](https://discordapp.com/channels/1107178041848909847/1148534730359308298). -2. Download the build artifacts from the channel. -3. Subsequently, to get the latest nightly, just quit and restart the app. -4. Upon app restart, you will be automatically prompted to update to the latest nightly build. - -## Manual Build - -Stable releases are triggered by manual builds. This is usually done for new features or a bug fixes. - -The process for this project is defined in [`.github/workflows/jan-electron-build-nightly.yml`](.github/workflows/jan-electron-build-nightly.yml) - ## Acknowledgements Jan builds on top of other open-source projects: diff --git a/core/src/api/index.ts b/core/src/api/index.ts index 3cf2693e7..be1b06777 100644 --- a/core/src/api/index.ts +++ b/core/src/api/index.ts @@ -4,13 +4,14 @@ */ export enum AppRoute { appDataPath = 'appDataPath', - appVersion = 'appVersion', openExternalUrl = 'openExternalUrl', openAppDirectory = 'openAppDirectory', openFileExplore = 'openFileExplorer', relaunch = 'relaunch', joinPath = 'joinPath', baseName = 'baseName', + startServer = 'startServer', + stopServer = 'stopServer', } export enum AppEvent { diff --git a/core/src/node/extension/manager.ts b/core/src/node/extension/manager.ts index 9173504a7..3e52ac02d 100644 --- a/core/src/node/extension/manager.ts +++ b/core/src/node/extension/manager.ts @@ -47,7 +47,7 @@ export class ExtensionManager { const extensionsJson = join(extDir, "extensions.json"); if (!existsSync(extensionsJson)) - writeFileSync(extensionsJson, "{}", "utf8"); + writeFileSync(extensionsJson, "{}"); this.extensionsPath = extDir; } catch (error) { diff --git a/core/src/node/extension/store.ts b/core/src/node/extension/store.ts index 8e11c2bb2..84b1f9caf 100644 --- a/core/src/node/extension/store.ts +++ b/core/src/node/extension/store.ts @@ -84,7 +84,6 @@ export function persistExtensions() { writeFileSync( ExtensionManager.instance.getExtensionsFile(), JSON.stringify(persistData), - "utf8" ); } diff --git a/core/src/node/index.ts b/core/src/node/index.ts index 49c2c3c26..50651d1fd 100644 --- a/core/src/node/index.ts +++ b/core/src/node/index.ts @@ -5,3 +5,4 @@ export * from './extension/store' export * from './download' export * from './module' export * from './api' +export * from './log' diff --git a/core/src/node/log.ts b/core/src/node/log.ts new file mode 100644 index 000000000..7291516cd --- /dev/null +++ b/core/src/node/log.ts @@ -0,0 +1,18 @@ +import fs from 'fs' +import util from 'util' +import path from 'path' +import os from 'os' + +const appDir = path.join(os.homedir(), 'jan') + +export const logPath = path.join(appDir, 'app.log') + +export const log = function (d: any) { + if (fs.existsSync(appDir)) { + var log_file = fs.createWriteStream(logPath, { + flags: 'a', + }) + log_file.write(util.format(d) + '\n') + log_file.close() + } +} diff --git a/core/src/node/module.ts b/core/src/node/module.ts index 2201fb400..0919667df 100644 --- a/core/src/node/module.ts +++ b/core/src/node/module.ts @@ -3,6 +3,7 @@ */ export class ModuleManager { public requiredModules: Record = {} + public cleaningResource = false public static instance: ModuleManager = new ModuleManager() diff --git a/docs/docs/developer/02-build-assistant/01-your-first-assistant.md b/docs/docs/developer/02-build-assistant/01-your-first-assistant.md index 11999981f..16b80fc5e 100644 --- a/docs/docs/developer/02-build-assistant/01-your-first-assistant.md +++ b/docs/docs/developer/02-build-assistant/01-your-first-assistant.md @@ -1,9 +1,23 @@ --- title: Your First Assistant +slug: /developer/build-assistant/your-first-assistant/ +description: A quick start on how to build an assistant. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + quick start, + build assistant, + ] --- :::caution This is currently under development. ::: -A quickstart on how to build an assistant diff --git a/docs/docs/developer/02-build-assistant/02-assistant-anatomy.md b/docs/docs/developer/02-build-assistant/02-assistant-anatomy.md index 6c5fc1035..e6951a05b 100644 --- a/docs/docs/developer/02-build-assistant/02-assistant-anatomy.md +++ b/docs/docs/developer/02-build-assistant/02-assistant-anatomy.md @@ -1,9 +1,22 @@ --- title: Anatomy of an Assistant +slug: /developer/build-assistant/assistant-anatomy/ +description: An overview of assistant.json +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build assistant, + assistant anatomy, + ] --- :::caution This is currently under development. ::: - -An overview of assistant.json diff --git a/docs/docs/developer/02-build-assistant/03-package-your-assistant.md b/docs/docs/developer/02-build-assistant/03-package-your-assistant.md index 8d384cb8a..12fa1510c 100644 --- a/docs/docs/developer/02-build-assistant/03-package-your-assistant.md +++ b/docs/docs/developer/02-build-assistant/03-package-your-assistant.md @@ -1,9 +1,22 @@ --- title: Package your Assistant +slug: /developer/build-assistant/package-your-assistant/ +description: Package your assistant for sharing and publishing. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + quick start, + build assistant, + ] --- :::caution This is currently under development. ::: - -Packaging, exporting, sharing, publishing an assistant to Hub diff --git a/docs/docs/developer/02-build-assistant/README.md b/docs/docs/developer/02-build-assistant/README.md deleted file mode 100644 index 28264ae9c..000000000 --- a/docs/docs/developer/02-build-assistant/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Build an Assistant ---- - -:::caution -This is currently under development. -::: - -In this tutorial you will learn: - -- -- diff --git a/docs/docs/developer/02-build-assistant/README.mdx b/docs/docs/developer/02-build-assistant/README.mdx new file mode 100644 index 000000000..29cf8b63d --- /dev/null +++ b/docs/docs/developer/02-build-assistant/README.mdx @@ -0,0 +1,25 @@ +--- +title: Build an Assistant +slug: /developer/build-assistant +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build assistant, + ] +--- + +:::caution +This is currently under development. +::: + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/developer/03-build-engine/01-your-first-engine.md b/docs/docs/developer/03-build-engine/01-your-first-engine.md index 8323354f8..0670d63c4 100644 --- a/docs/docs/developer/03-build-engine/01-your-first-engine.md +++ b/docs/docs/developer/03-build-engine/01-your-first-engine.md @@ -1,9 +1,24 @@ --- -title: Your First Assistant +title: Your First Engine +slug: /developer/build-engine/your-first-engine/ +description: A quick start on how to build your first engine +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + quick start, + build engine, + ] --- :::caution This is currently under development. ::: -A quickstart on how to integrate tensorrt llm +A quickstart on how to integrate tensorrt llm \ No newline at end of file diff --git a/docs/docs/developer/03-build-engine/02-engine-anatomy.md b/docs/docs/developer/03-build-engine/02-engine-anatomy.md index 45c657141..1e7f559da 100644 --- a/docs/docs/developer/03-build-engine/02-engine-anatomy.md +++ b/docs/docs/developer/03-build-engine/02-engine-anatomy.md @@ -1,9 +1,22 @@ --- title: Anatomy of an Engine +slug: /developer/build-engine/engine-anatomy +description: An overview of engine.json +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build engine, + engine anatomy, + ] --- :::caution This is currently under development. ::: - -An overview of engine.json diff --git a/docs/docs/developer/03-build-engine/03-package-your-engine.md b/docs/docs/developer/03-build-engine/03-package-your-engine.md index d6acfd6fd..794e1abb2 100644 --- a/docs/docs/developer/03-build-engine/03-package-your-engine.md +++ b/docs/docs/developer/03-build-engine/03-package-your-engine.md @@ -1,9 +1,22 @@ --- -title: Package your Extension +title: Package your Engine +slug: /developer/build-engine/package-your-engine/ +description: Package your engine for sharing and publishing. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build engine, + engine anatomy, + ] --- :::caution This is currently under development. ::: - -Packaging, exporting, sharing, publishing an engine config to Hub diff --git a/docs/docs/developer/03-build-engine/README.md b/docs/docs/developer/03-build-engine/README.md deleted file mode 100644 index fa9f486fe..000000000 --- a/docs/docs/developer/03-build-engine/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Build an Inference Engine ---- - -:::caution -This is currently under development. -::: - -In this tutorial you will learn: - -- -- diff --git a/docs/docs/developer/03-build-engine/README.mdx b/docs/docs/developer/03-build-engine/README.mdx new file mode 100644 index 000000000..a2521ff54 --- /dev/null +++ b/docs/docs/developer/03-build-engine/README.mdx @@ -0,0 +1,25 @@ +--- +title: Build an Inference Engine +slug: /developer/build-engine/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build assistant, + ] +--- + +:::caution +This is currently under development. +::: + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/developer/04-build-extension/01-your-first-extension.md b/docs/docs/developer/04-build-extension/01-your-first-extension.md index 0e5241e3c..6ffe2de2f 100644 --- a/docs/docs/developer/04-build-extension/01-your-first-extension.md +++ b/docs/docs/developer/04-build-extension/01-your-first-extension.md @@ -1,9 +1,22 @@ --- title: Your First Extension +slug: /developer/build-extension/your-first-extension/ +description: A quick start on how to build your first extension +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + quick start, + build extension, + ] --- :::caution This is currently under development. ::: - -A quickstart on tensorrt-llm impl diff --git a/docs/docs/developer/04-build-extension/02-extension-anatomy.md b/docs/docs/developer/04-build-extension/02-extension-anatomy.md index 55b3def4b..7c3cd1911 100644 --- a/docs/docs/developer/04-build-extension/02-extension-anatomy.md +++ b/docs/docs/developer/04-build-extension/02-extension-anatomy.md @@ -1,9 +1,22 @@ --- title: Anatomy of an Extension +slug: /developer/build-extension/extension-anatomy +description: An overview of extensions.json +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build extension, + extension anatomy, + ] --- :::caution This is currently under development. ::: - -An overview of engine.json diff --git a/docs/docs/developer/04-build-extension/03-package-your-assistant.md b/docs/docs/developer/04-build-extension/03-package-your-assistant.md deleted file mode 100644 index c32a24a67..000000000 --- a/docs/docs/developer/04-build-extension/03-package-your-assistant.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Package your Extension ---- - -:::caution -This is currently under development. -::: - -Packaging, exporting, sharing, publishing an extension to Hub diff --git a/docs/docs/developer/04-build-extension/03-package-your-extension.md b/docs/docs/developer/04-build-extension/03-package-your-extension.md new file mode 100644 index 000000000..cf7ffc6ba --- /dev/null +++ b/docs/docs/developer/04-build-extension/03-package-your-extension.md @@ -0,0 +1,22 @@ +--- +title: Package your Engine +slug: /developer/build-extension/package-your-extension/ +description: Package your extension for sharing and publishing. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build extension, + extension anatomy, + ] +--- + +:::caution +This is currently under development. +::: diff --git a/docs/docs/developer/04-build-extension/README.md b/docs/docs/developer/04-build-extension/README.md deleted file mode 100644 index fb759164a..000000000 --- a/docs/docs/developer/04-build-extension/README.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Build an Extension ---- - -# Overview - -:::caution -This is currently under development. -::: diff --git a/docs/docs/developer/04-build-extension/README.mdx b/docs/docs/developer/04-build-extension/README.mdx new file mode 100644 index 000000000..a981281e7 --- /dev/null +++ b/docs/docs/developer/04-build-extension/README.mdx @@ -0,0 +1,25 @@ +--- +title: Build an Extension +slug: /developer/build-extension/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + build extension, + ] +--- + +:::caution +This is currently under development. +::: + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/docs/README.md b/docs/docs/docs/README.md index d6ff08d55..a82a8a801 100644 --- a/docs/docs/docs/README.md +++ b/docs/docs/docs/README.md @@ -3,4 +3,69 @@ title: Overview slug: /docs --- -Hello world +The following low-level docs are aimed at core contributors and cover how to contribute to the Core SDK. + +:::tip +If you are interested to **build on top of the SDK**, like creating assistants or adding app level extensions, please refer to [developer docs](/developer) instead. +::: + +## Core SDK + +At its Core, Jan is a cross-platform, local-first and AI native framework that can be used to build anything. In fact, current features are all implemented as 3rd party extensions on top of this Core SDK. + +Ultimately, we aim for a VSCode or Obsidian like framework that allows devs to build and customize complex AI applications for their specific needs, in less than 15 minutes. + +### Cross Platform + +Jan follows [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) to the best of our ability. Though leaky abstractions remain (we're a fast moving, open source codebase), we do our best to build an SDK that allows devs to **build once, deploy everywhere.** + +Currently, Jan supports: + +- `Node Native Runtime`, good for server side apps +- `Electron Chromium`, good for Desktop Native apps +- `Capacitor`, good for Mobile apps (planned, not built yet) +- `Python Runtime`, good for MLOps workflows (planned, not built yet) + +Currently, Jan works across: + +- Mac Intel & Silicon +- Windows +- Ubuntu +- Nvidia GPUs + +Read more: + +- [Code Entrypoint](https://github.com/janhq/jan/tree/main/core) +- [Dependency Inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle) + +### Local First + +Jan's data persistence happens on the user's local filesystem. + +We implemented abstractions on top of `fs` and other core modules in an opinionated way, s.t. user data is saved in a folder-based framework that lets users easily package, export, and manage their data. + +Read more: + +- [Folder-based fs wrapper](https://github.com/janhq/jan/blob/main/core/src/fs.ts) +- [Piping Node modules across infrastructures](https://github.com/janhq/jan/tree/main/core/src/node) + +### AI Native + +All software applications can be natively supercharged with an embedded AI server and AI abstractions. + +Including: + +- OpenAI Compatible AI [types](https://github.com/janhq/jan/tree/main/core/src/types) and [core extensions](https://github.com/janhq/jan/tree/main/core/src/extensions) to support common functionality like making an inference call. +- A lightweight, embedded C++ [inference engine](https://github.com/janhq/jan/tree/main/extensions/inference-nitro-extension) that's immediately callable from code. + +- [Code Entrypoint](https://github.com/janhq/jan/tree/main/core/src/api) + +## Fun Project Ideas + +Beyond the current Jan client and UX, the Core SDK can be used to build many other AI-powered and privacy preserving applications. + +- `Game engine`: For AI enabled character games, procedural generation games +- `Health app`: For a personal healthcare app that improves habits +- Got ideas? Make a PR into this docs page! + +If you are interested to tackle these issues, or have suggestions for integrations and other OSS tools we can use, please hit us up in [Discord](https://discord.gg/5rQ2zTv3be). diff --git a/docs/docs/docs/engineering/README.md b/docs/docs/docs/engineering/README.md deleted file mode 100644 index 3c87ba845..000000000 --- a/docs/docs/docs/engineering/README.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Engineering Specs ---- - -Talk about CoreSDK here diff --git a/docs/docs/docs/engineering/README.mdx b/docs/docs/docs/engineering/README.mdx new file mode 100644 index 000000000..2fd9a5cc1 --- /dev/null +++ b/docs/docs/docs/engineering/README.mdx @@ -0,0 +1,24 @@ +--- +title: Engineering Specs +slug: /docs/engineering +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + spec, + engineering, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + + +Talk about CoreSDK here diff --git a/docs/docs/docs/product/README.md b/docs/docs/docs/product/README.md deleted file mode 100644 index 5a00a0cf2..000000000 --- a/docs/docs/docs/product/README.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Product Specs ---- diff --git a/docs/docs/docs/product/README.mdx b/docs/docs/docs/product/README.mdx new file mode 100644 index 000000000..cce274f22 --- /dev/null +++ b/docs/docs/docs/product/README.mdx @@ -0,0 +1,22 @@ +--- +title: Product Specs +slug: /docs/product +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + spec, + product, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/guides/03-chatting/README.md b/docs/docs/guides/03-chatting/README.mdx similarity index 78% rename from docs/docs/guides/03-chatting/README.md rename to docs/docs/guides/03-chatting/README.mdx index 7317f1105..6b5550964 100644 --- a/docs/docs/guides/03-chatting/README.md +++ b/docs/docs/guides/03-chatting/README.mdx @@ -18,7 +18,6 @@ keywords: This guide is designed to help you maximize your experience with Jan, covering everything from starting engaging threads to managing your chat history effectively. -- [Start a thread](start-thread) -- [Upload docs](upload-docs) -- [Upload images](upload-images) -- [Manage chat history](manage-chat-history) \ No newline at end of file +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/guides/04-using-models/01-install-from-hub.mdx b/docs/docs/guides/04-using-models/01-install-from-hub.mdx index 414e57fa1..1f1b97b0c 100644 --- a/docs/docs/guides/04-using-models/01-install-from-hub.mdx +++ b/docs/docs/guides/04-using-models/01-install-from-hub.mdx @@ -1,6 +1,6 @@ --- title: Install Models from the Hub -description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +description: Guide to install models from the Hub. keywords: [ Jan AI, diff --git a/docs/docs/guides/04-using-models/02-import-manually.mdx b/docs/docs/guides/04-using-models/02-import-manually.mdx index 6fc7e04a3..b2a7158bf 100644 --- a/docs/docs/guides/04-using-models/02-import-manually.mdx +++ b/docs/docs/guides/04-using-models/02-import-manually.mdx @@ -1,7 +1,7 @@ --- title: Import Models Manually slug: /guides/using-models/import-manually -description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +description: Guide to manually import a local model into Jan. keywords: [ Jan AI, @@ -13,6 +13,7 @@ keywords: no-subscription fee, large language model, import-models-manually, + local model, ] --- @@ -24,16 +25,12 @@ This is currently under development. import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; -Jan is compatible with all GGUF models. +## Steps to Manually Import a Local Model -If you can not find the model you want in the Hub or have a custom model you want to use, you can import it manually. - -In this guide, we will show you how to import a GGUF model from [HuggingFace](https://huggingface.co/), using our lastest model, [Trinity](https://huggingface.co/janhq/trinity-v1-GGUF), as an example. +In this section, we will show you how to import a GGUF model from [HuggingFace](https://huggingface.co/), using our latest model, [Trinity](https://huggingface.co/janhq/trinity-v1-GGUF), as an example. > We are fast shipping a UI to make this easier, but it's a bit manual for now. Apologies. -## Steps to Manually Import a Model - ### 1. Create a Model Folder Navigate to the `~/jan/models` folder. You can find this folder by going to `App Settings` > `Advanced` > `Open App Directory`. @@ -126,45 +123,45 @@ Edit `model.json` and include the following configurations: - Ensure the filename must be `model.json`. - Ensure the `id` property matches the folder name you created. - Ensure the GGUF filename should match the `id` property exactly. -- Ensure the `source_url` property is the direct binary download link ending in `.gguf`. In HuggingFace, you can find the direct links in `Files and versions` tab. +- Ensure the `source_url` property is the direct binary download link ending in `.gguf`. In HuggingFace, you can find the direct links in the `Files and versions` tab. - Ensure you are using the correct `prompt_template`. This is usually provided in the HuggingFace model's description page. - Ensure the `state` property is set to `ready`. ```js { - // highlight-start - "source_url": "https://huggingface.co/janhq/trinity-v1-GGUF/resolve/main/trinity-v1.Q4_K_M.gguf", - "id": "trinity-v1-7b", - // highlight-end - "object": "model", - "name": "Trinity-v1 7B Q4", - "version": "1.0", - "description": "Trinity is an experimental model merge of GreenNodeLM & LeoScorpius using the Slerp method. Recommended for daily assistance purposes.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - // highlight-next-line - "prompt_template": "{system_message}\n### Instruction:\n{prompt}\n### Response:" - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "Jan", - "tags": ["7B", "Merged"], - "size": 4370000000 - }, + // highlight-start + "source_url": "https://huggingface.co/janhq/trinity-v1-GGUF/resolve/main/trinity-v1.Q4_K_M.gguf", + "id": "trinity-v1-7b", + // highlight-end + "object": "model", + "name": "Trinity-v1 7B Q4", + "version": "1.0", + "description": "Trinity is an experimental model merge of GreenNodeLM & LeoScorpius using the Slerp method. Recommended for daily assistance purposes.", + "format": "gguf", + "settings": { + "ctx_len": 4096, // highlight-next-line - "state": "ready", - "engine": "nitro" - } + "prompt_template": "{system_message}\n### Instruction:\n{prompt}\n### Response:" + }, + "parameters": { + "max_tokens": 4096 + }, + "metadata": { + "author": "Jan", + "tags": ["7B", "Merged"], + "size": 4370000000 + }, + "engine": "nitro", + // highlight-next-line + "state": "ready" +} ``` ### 3. Download the Model Restart Jan and navigate to the Hub. Locate your model and click the `Download` button to download the model binary. -![image](assets/download-model.png) +![image-01](assets/02-manually-import-local-model.png) Your model is now ready to use in Jan. diff --git a/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx b/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx new file mode 100644 index 000000000..660b4b044 --- /dev/null +++ b/docs/docs/guides/04-using-models/03-integrate-with-remote-server.mdx @@ -0,0 +1,148 @@ +--- +title: Integrate With a Remote Server +slug: /guides/using-models/integrate-with-remote-server +description: Guide to integrate with a remote server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + import-models-manually, + remote server, + OAI compatible, + ] +--- + +:::caution +This is currently under development. +::: + +In this guide, we will show you how to configure Jan as a client and point it to any remote & local (self-hosted) API server. + +## OpenAI Platform Configuration + +In this section, we will show you how to configure with OpenAI Platform, using the OpenAI GPT 3.5 Turbo 16k model as an example. + +### 1. Create a Model JSON + +Navigate to the `~/jan/models` folder. Create a folder named `gpt-3.5-turbo-16k` and create a `model.json` file inside the folder including the following configurations: + +- Ensure the filename must be `model.json`. +- Ensure the `id` property matches the folder name you created. +- Ensure the `format` property is set to `api`. +- Ensure the `engine` property is set to `openai`. +- Ensure the `state` property is set to `ready`. + +```js +{ + "source_url": "https://openai.com", + // highlight-next-line + "id": "gpt-3.5-turbo-16k", + "object": "model", + "name": "OpenAI GPT 3.5 Turbo 16k", + "version": "1.0", + "description": "OpenAI GPT 3.5 Turbo 16k model is extremely good", + // highlight-start + "format": "api", + "settings": {}, + "parameters": {}, + "metadata": { + "author": "OpenAI", + "tags": ["General", "Big Context Length"] + }, + "engine": "openai", + "state": "ready" + // highlight-end +} +``` + +### 2. Configure OpenAI API Keys + +You can find your API keys in the [OpenAI Platform](https://platform.openai.com/api-keys) and set the OpenAI API keys in `~/jan/engines/openai.json` file. + +```js +{ + "full_url": "https://api.openai.com/v1/chat/completions", + // highlight-next-line + "api_key": "sk-" +} +``` + +### 3. Start the Model + +Restart Jan and navigate to the Hub. Then, select your configured model and start the model. + +![image-01](assets/03-openai-platform-configuration.png) + +## Engines with OAI Compatible Configuration + +In this section, we will show you how to configure a client connection to a remote/local server, using Jan's API server that is running model `mistral-ins-7b-q4` as an example. + +### 1. Configure a Client Connection + +Navigate to the `~/jan/engines` folder and modify the `openai.json` file. Please note that at the moment the code that supports any openai compatible endpoint only reads `engine/openai.json` file, thus, it will not search any other files in this directory. + +Configure `full_url` properties with the endpoint server that you want to connect. For example, if you want to connect to Jan's API server, you can configure it as follows: + +```js +{ + // highlight-start + // "full_url": "https://:/v1/chat/completions" + "full_url": "https://:1337/v1/chat/completions", + // highlight-end + // Skip api_key if your local server does not require authentication + // "api_key": "sk-" +} +``` + +### 2. Create a Model JSON + +Navigate to the `~/jan/models` folder. Create a folder named `mistral-ins-7b-q4` and create a `model.json` file inside the folder including the following configurations: + +- Ensure the filename must be `model.json`. +- Ensure the `id` property matches the folder name you created. +- Ensure the `format` property is set to `api`. +- Ensure the `engine` property is set to `openai`. +- Ensure the `state` property is set to `ready`. + +```js +{ + "source_url": "https://jan.ai", + // highlight-next-line + "id": "mistral-ins-7b-q4", + "object": "model", + "name": "Mistral Instruct 7B Q4 on Jan API Server", + "version": "1.0", + "description": "Jan integration with remote Jan API server", + // highlight-next-line + "format": "api", + "settings": {}, + "parameters": {}, + "metadata": { + "author": "MistralAI, The Bloke", + "tags": [ + "remote", + "awesome" + ] + }, + // highlight-start + "engine": "openai", + "state": "ready" + // highlight-end +} +``` + +### 3. Start the Model + +Restart Jan and navigate to the Hub. Locate your model and click the Use button. + +![image-02](assets/03-oai-compatible-configuration.png) + +## Assistance and Support + +If you have questions or are looking for more preconfigured GGUF models, please feel free to join our [Discord community](https://discord.gg/Dt7MxDyNNZ) for support, updates, and discussions. diff --git a/docs/docs/guides/04-using-models/03-customize-models.md b/docs/docs/guides/04-using-models/04-customize-models.md similarity index 100% rename from docs/docs/guides/04-using-models/03-customize-models.md rename to docs/docs/guides/04-using-models/04-customize-models.md diff --git a/docs/docs/guides/04-using-models/04-package-models.md b/docs/docs/guides/04-using-models/05-package-models.md similarity index 100% rename from docs/docs/guides/04-using-models/04-package-models.md rename to docs/docs/guides/04-using-models/05-package-models.md diff --git a/docs/docs/guides/04-using-models/README.md b/docs/docs/guides/04-using-models/README.md deleted file mode 100644 index ad958633d..000000000 --- a/docs/docs/guides/04-using-models/README.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Using Models ---- diff --git a/docs/docs/guides/04-using-models/README.mdx b/docs/docs/guides/04-using-models/README.mdx new file mode 100644 index 000000000..3c1ee7778 --- /dev/null +++ b/docs/docs/guides/04-using-models/README.mdx @@ -0,0 +1,21 @@ +--- +title: Using Models +slug: /guides/using-models/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + using-models, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/guides/04-using-models/assets/download-model.png b/docs/docs/guides/04-using-models/assets/02-manually-import-local-model.png similarity index 100% rename from docs/docs/guides/04-using-models/assets/download-model.png rename to docs/docs/guides/04-using-models/assets/02-manually-import-local-model.png diff --git a/docs/docs/guides/04-using-models/assets/03-oai-compatible-configuration.png b/docs/docs/guides/04-using-models/assets/03-oai-compatible-configuration.png new file mode 100644 index 000000000..a298d9405 Binary files /dev/null and b/docs/docs/guides/04-using-models/assets/03-oai-compatible-configuration.png differ diff --git a/docs/docs/guides/04-using-models/assets/03-openai-platform-configuration.png b/docs/docs/guides/04-using-models/assets/03-openai-platform-configuration.png new file mode 100644 index 000000000..6300b20f0 Binary files /dev/null and b/docs/docs/guides/04-using-models/assets/03-openai-platform-configuration.png differ diff --git a/docs/docs/guides/05-using-server/01-server.md b/docs/docs/guides/05-using-server/01-server.md index aa5bf8d3a..3c60419ef 100644 --- a/docs/docs/guides/05-using-server/01-server.md +++ b/docs/docs/guides/05-using-server/01-server.md @@ -1,6 +1,6 @@ --- title: Connect to Server -description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +description: Connect to Jan's built-in API server. keywords: [ Jan AI, @@ -20,13 +20,14 @@ This page is under construction. ::: -Jan ships with a built-in API server, that can be used as a drop-in replacement for OpenAI's API. +Jan ships with a built-in API server, that can be used as a drop-in, local replacement for OpenAI's API. -Jan runs on port `1337` by default, but this can be changed in Settings. +Jan runs on port `1337` by default, but this can (soon) be changed in Settings. -Check out the [API Reference](/api-reference) for more information on the API endpoints. +1. Go to Settings > Advanced > Enable API Server -``` -curl http://localhost:1337/v1/chat/completions +2. Go to http://localhost:1337/docs for API docs. -``` +3. In terminal, simply CURL... + +Note: Some UI states may be broken when in Server Mode. diff --git a/docs/docs/guides/05-using-server/README.md b/docs/docs/guides/05-using-server/README.md deleted file mode 100644 index 500f2bb27..000000000 --- a/docs/docs/guides/05-using-server/README.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Using the Local Server ---- diff --git a/docs/docs/guides/05-using-server/README.mdx b/docs/docs/guides/05-using-server/README.mdx new file mode 100644 index 000000000..79ccad1f6 --- /dev/null +++ b/docs/docs/guides/05-using-server/README.mdx @@ -0,0 +1,21 @@ +--- +title: Using the Local Server +slug: /guides/using-server/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + using-server, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/guides/06-using-extensions/01-import-extensions.md b/docs/docs/guides/06-using-extensions/01-import-extensions.md index 69cc14cc1..52e26c7d0 100644 --- a/docs/docs/guides/06-using-extensions/01-import-extensions.md +++ b/docs/docs/guides/06-using-extensions/01-import-extensions.md @@ -1,3 +1,17 @@ --- title: Import Extensions ---- +slug: /guides/using-extensions/import-extensions/ +description: Import extensions into Jan. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + using-models, + ] +--- \ No newline at end of file diff --git a/docs/docs/guides/06-using-extensions/02-extension-settings.md b/docs/docs/guides/06-using-extensions/02-extension-settings.md index 6892a4c93..d5b053b72 100644 --- a/docs/docs/guides/06-using-extensions/02-extension-settings.md +++ b/docs/docs/guides/06-using-extensions/02-extension-settings.md @@ -1,5 +1,17 @@ --- title: Extension Settings ---- - -TODO: how to configure settings for extensions +slug: /guides/using-extensions/extension-settings/ +description: Configure settings for extensions. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + using-models, + ] +--- \ No newline at end of file diff --git a/docs/docs/guides/06-using-extensions/README.md b/docs/docs/guides/06-using-extensions/README.md deleted file mode 100644 index c2221099f..000000000 --- a/docs/docs/guides/06-using-extensions/README.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Using Extensions ---- diff --git a/docs/docs/guides/06-using-extensions/README.mdx b/docs/docs/guides/06-using-extensions/README.mdx new file mode 100644 index 000000000..015fc5948 --- /dev/null +++ b/docs/docs/guides/06-using-extensions/README.mdx @@ -0,0 +1,21 @@ +--- +title: Using Extensions +slug: /guides/using-extensions/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + using-extensions, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/guides/07-troubleshooting/01-stuck-on-broken-build.mdx b/docs/docs/guides/07-troubleshooting/01-stuck-on-broken-build.mdx index 527588092..5a138adb6 100644 --- a/docs/docs/guides/07-troubleshooting/01-stuck-on-broken-build.mdx +++ b/docs/docs/guides/07-troubleshooting/01-stuck-on-broken-build.mdx @@ -1,7 +1,7 @@ --- title: Stuck on a Broken Build slug: /troubleshooting/stuck-on-broken-build -description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +description: Troubleshooting steps to resolve issues related to broken builds. keywords: [ Jan AI, diff --git a/docs/docs/guides/07-troubleshooting/README.md b/docs/docs/guides/07-troubleshooting/README.md deleted file mode 100644 index 9662ac078..000000000 --- a/docs/docs/guides/07-troubleshooting/README.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -title: Troubleshooting ---- diff --git a/docs/docs/guides/07-troubleshooting/README.mdx b/docs/docs/guides/07-troubleshooting/README.mdx new file mode 100644 index 000000000..2b8a3b89f --- /dev/null +++ b/docs/docs/guides/07-troubleshooting/README.mdx @@ -0,0 +1,21 @@ +--- +title: Troubleshooting +slug: /guides/troubleshooting/ +description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server. +keywords: + [ + Jan AI, + Jan, + ChatGPT alternative, + local AI, + private AI, + conversational AI, + no-subscription fee, + large language model, + troubleshooting, + ] +--- + +import DocCardList from "@theme/DocCardList"; + + diff --git a/docs/docs/handbook/onboarding.md b/docs/docs/handbook/onboarding.md index e9124bdad..3aec99258 100644 --- a/docs/docs/handbook/onboarding.md +++ b/docs/docs/handbook/onboarding.md @@ -32,7 +32,7 @@ Welcome to Jan! We’re really excited to bring you onboard. - We operate on the basis of trust. - We expect you to be available and communicative during scheduled meetings or work hours. - Turning on video during meetings is encouraged. -- Casual dress during meetings is acceptable; however, use discretion (No naked top, pajamas, etc.) +- Casual dress during meetings is acceptable; however, use discretion (No nudity, pajamas, etc.) - While it’s natural for people to disagree at times, disagreement is no excuse for poor behavior and poor manners. We cannot allow that frustration to turn into a personal attack. - Respect other people's cultures. Especially since we are working in a diverse working culture. - Sexual harassment is a specific type of prohibited conduct. Sexual harassment is any unwelcome conduct of a sexual nature that might reasonably be expected or be perceived to cause offense or humiliation. Sexual harassment may involve any conduct of a verbal, nonverbal, or physical nature, including written and electronic communications, and may occur between persons of the same or different genders. diff --git a/docs/docs/handbook/product.md b/docs/docs/handbook/product.md index 7731f4bf5..e8d114691 100644 --- a/docs/docs/handbook/product.md +++ b/docs/docs/handbook/product.md @@ -6,41 +6,54 @@ We use the [Jan Monorepo Project](https://github.com/orgs/janhq/projects/5) in G As much as possible, everyone owns their respective `epics` and `tasks`. -> We aim for a `loosely coupled, but tightly aligned` autonomous culture. +:::tip +We aim for a `loosely coupled, but tightly aligned` autonomous culture. +::: + +## Quicklinks + +- [High-level roadmap](https://github.com/orgs/janhq/projects/5/views/16): view used at at strategic level, for team wide alignment. Start & end dates reflect engineering implementation cycles. Typically product & design work preceeds these timelines. +- [Standup Kanban](https://github.com/orgs/janhq/projects/5/views/25): view used during daily standup. Sprints should be up to date. ## Organization -[`Project Labels`](https://github.com/janhq/jan/issues/labels) +[`Roadmap Labels`](https://github.com/janhq/jan/labels?q=roadmap) -- `Project Labels` tag large, long-term, & strategic projects that can span multiple teams and multiple sprints -- Example label: `project: Jan has Mobile` -- `Projects` contain `epics` +- `Roadmap Labels` tag large, long-term, & strategic projects that can span multiple teams and multiple sprints +- Example label: `roadmap: Jan has Mobile` +- `Roadmaps` contain `epics` [`Epics`](https://github.com/janhq/jan/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+epic%22) - `Epics` track large stories that span 1-2 weeks, and it outlines specs, architecture decisions, designs -- Each `epic` corresponds with a `milestone` - `Epics` contain `tasks` - `Epics` should always have 1 owner [`Milestones`](https://github.com/janhq/jan/milestones) -- `Milestones` correspond 1:1 to `epics` and are used to filter [Roadmap Views](https://github.com/orgs/janhq/projects/5/views/16) -- `Milestones` span 1-2 weeks and have deadlines +- `Milestones` track release versions. We use [semantic versioning](https://semver.org/) +- `Milestones` span ~2 weeks and have deadlines +- `Milestones` usually fit within 2 week sprint cycles [`Tasks`](https://github.com/janhq/jan/issues) - Tasks are individual issues (feats, bugs, chores) that can be completed within a few days -- Tasks under `In-progress` and `Todo` should always belong to a `milestone` +- Tasks, except for critical bugs, should always belong to an `epic` (and thus fit into our roadmap) - Tasks are usually named per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) - Tasks should always have 1 owner -We aim to always work on `tasks` that belong to a `milestones`. +We aim to always sprint on `tasks` that are a part of the [current roadmap](https://github.com/orgs/janhq/projects/5/views/16). -## Task Status +## Kanban -- `triaged`: issues that have been assigned -- `todo`: issues you plan to tackle within this week +- `no status`: issues that need to be triaged (needs an owner, ETA) +- `icebox`: issues you don't plan to tackle yet +- `planned`: issues you plan to tackle this week - `in-progress`: in progress - `in-review`: pending PR or blocked by something - `done`: done + +## Triage SOP + +- `Urgent bugs`: assign to an owner (or @engineers if you are not sure) && tag the current `sprint` & `milestone` +- `All else`: assign the correct roadmap `label(s)` and owner (if any) diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css new file mode 100644 index 000000000..115bc3f21 --- /dev/null +++ b/docs/src/css/custom.css @@ -0,0 +1,3 @@ +.DocCardList--no-description .card p { + display: none; +} \ No newline at end of file diff --git a/docs/src/css/custom_toc.css b/docs/src/css/custom_toc.css index 68abfb866..2410f7a06 100644 --- a/docs/src/css/custom_toc.css +++ b/docs/src/css/custom_toc.css @@ -2,4 +2,4 @@ font-weight: bold; margin-bottom: 16px; margin-top: -20px; -} +} \ No newline at end of file diff --git a/docs/src/styles/main.scss b/docs/src/styles/main.scss index 216b960d3..4c6fc7151 100644 --- a/docs/src/styles/main.scss +++ b/docs/src/styles/main.scss @@ -11,3 +11,5 @@ @import "./tweaks/markdown.scss"; @import "./tweaks/redocusaurus.scss"; @import "./tweaks/sidebar.scss"; + +@import "../css/custom.css"; \ No newline at end of file diff --git a/docs/src/styles/tweaks/markdown.scss b/docs/src/styles/tweaks/markdown.scss index 7e8e33871..1093f2318 100644 --- a/docs/src/styles/tweaks/markdown.scss +++ b/docs/src/styles/tweaks/markdown.scss @@ -1,6 +1,6 @@ .theme-doc-markdown { a { - @apply text-blue-600 dark:text-blue-400 underline; + @apply text-blue-600 dark:text-blue-400; } ul { list-style: revert; diff --git a/electron/docs/openapi/.gitkeep b/electron/docs/openapi/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/electron/handlers/app.ts b/electron/handlers/app.ts index 726ed612e..d0d9dae72 100644 --- a/electron/handlers/app.ts +++ b/electron/handlers/app.ts @@ -1,20 +1,12 @@ import { app, ipcMain, shell, nativeTheme } from 'electron' import { join, basename } from 'path' import { WindowManager } from './../managers/window' -import { userSpacePath } from './../utils/path' +import { getResourcePath, userSpacePath } from './../utils/path' import { AppRoute } from '@janhq/core' import { ExtensionManager, ModuleManager } from '@janhq/core/node' +import { startServer, stopServer } from '@janhq/server' export function handleAppIPCs() { - /** - * Returns the version of the app. - * @param _event - The IPC event object. - * @returns The version of the app. - */ - ipcMain.handle(AppRoute.appVersion, async (_event) => { - return app.getVersion() - }) - /** * 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. @@ -56,6 +48,23 @@ export function handleAppIPCs() { basename(path) ) + /** + * Start Jan API Server. + */ + ipcMain.handle(AppRoute.startServer, async (_event) => + startServer( + app.isPackaged + ? join(getResourcePath(), 'docs', 'openapi', 'jan.yaml') + : undefined, + app.isPackaged ? join(getResourcePath(), 'docs', 'openapi') : undefined + ) + ) + + /** + * Stop Jan API Server. + */ + ipcMain.handle(AppRoute.stopServer, async (_event) => stopServer()) + /** * Relaunches the app in production - reload window in development. * @param _event - The IPC event object. diff --git a/electron/handlers/download.ts b/electron/handlers/download.ts index 621d85043..e8867b055 100644 --- a/electron/handlers/download.ts +++ b/electron/handlers/download.ts @@ -34,8 +34,18 @@ export function handleDownloaderIPCs() { */ ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => { const rq = DownloadManager.instance.networkRequests[fileName] - DownloadManager.instance.networkRequests[fileName] = undefined - rq?.abort() + if (rq) { + DownloadManager.instance.networkRequests[fileName] = undefined + rq?.abort() + } else { + WindowManager?.instance.currentWindow?.webContents.send( + DownloadEvent.onFileDownloadError, + { + fileName, + err: { message: 'aborted' }, + } + ) + } }) /** @@ -54,7 +64,11 @@ export function handleDownloaderIPCs() { } const destination = resolve(userDataPath, fileName) const rq = request(url) - // downloading file to a temp file first + + // Put request to download manager instance + DownloadManager.instance.setRequest(fileName, rq) + + // Downloading file to a temp file first const downloadingTempFile = `${destination}.download` progress(rq, {}) @@ -93,13 +107,11 @@ export function handleDownloaderIPCs() { DownloadEvent.onFileDownloadError, { fileName, - err: 'Download cancelled', + err: { message: 'aborted' }, } ) } }) .pipe(createWriteStream(downloadingTempFile)) - - DownloadManager.instance.setRequest(fileName, rq) }) } diff --git a/electron/main.ts b/electron/main.ts index fae3a1ffa..257842cf5 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -19,11 +19,7 @@ import { handleAppIPCs } from './handlers/app' import { handleAppUpdates } from './handlers/update' import { handleFsIPCs } from './handlers/fs' import { migrateExtensions } from './utils/migration' - -/** - * Server - */ -import { startServer } from '@janhq/server' +import { dispose } from './utils/disposable' app .whenReady() @@ -34,7 +30,6 @@ app .then(handleIPCs) .then(handleAppUpdates) .then(createMainWindow) - .then(startServer) .then(() => { app.on('activate', () => { if (!BrowserWindow.getAllWindows().length) { @@ -43,14 +38,12 @@ app }) }) -app.on('window-all-closed', () => { - ModuleManager.instance.clearImportedModules() - app.quit() +app.once('window-all-closed', () => { + cleanUpAndQuit() }) -app.on('quit', () => { - ModuleManager.instance.clearImportedModules() - app.quit() +app.once('quit', () => { + cleanUpAndQuit() }) function createMainWindow() { @@ -75,6 +68,12 @@ function createMainWindow() { if (process.platform !== 'darwin') app.quit() }) + /* Open external links in the default browser */ + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + require('electron').shell.openExternal(url) + return { action: 'deny' } + }) + /* Enable dev tools for development */ if (!app.isPackaged) mainWindow.webContents.openDevTools() } @@ -89,3 +88,13 @@ function handleIPCs() { handleAppIPCs() handleFileMangerIPCs() } + +function cleanUpAndQuit() { + if (!ModuleManager.instance.cleaningResource) { + ModuleManager.instance.cleaningResource = true + WindowManager.instance.currentWindow?.destroy() + dispose(ModuleManager.instance.requiredModules) + ModuleManager.instance.clearImportedModules() + app.quit() + } +} diff --git a/electron/package.json b/electron/package.json index 3cc5e7680..af98aa3b8 100644 --- a/electron/package.json +++ b/electron/package.json @@ -14,11 +14,13 @@ "build/*.{js,map}", "build/**/*.{js,map}", "pre-install", - "models/**/*" + "models/**/*", + "docs/**/*" ], "asarUnpack": [ "pre-install", - "models" + "models", + "docs" ], "publish": [ { diff --git a/extensions/inference-nitro-extension/package.json b/extensions/inference-nitro-extension/package.json index b2a143121..a35c51e58 100644 --- a/extensions/inference-nitro-extension/package.json +++ b/extensions/inference-nitro-extension/package.json @@ -32,9 +32,9 @@ "@janhq/core": "file:../../core", "download-cli": "^1.1.1", "fetch-retry": "^5.0.6", + "os-utils": "^0.0.14", "path-browserify": "^1.0.1", "rxjs": "^7.8.1", - "systeminformation": "^5.21.20", "tcp-port-used": "^1.0.2", "ts-loader": "^9.5.0", "ulid": "^2.3.0" @@ -50,6 +50,6 @@ "bundleDependencies": [ "tcp-port-used", "fetch-retry", - "systeminformation" + "os-utils" ] } diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index f0fdc7d7d..7678bae19 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -4,7 +4,7 @@ const path = require("path"); const { exec, spawn } = require("child_process"); const tcpPortUsed = require("tcp-port-used"); const fetchRetry = require("fetch-retry")(global.fetch); -const si = require("systeminformation"); +const osUtils = require("os-utils"); const { readFileSync, writeFileSync, existsSync } = require("fs"); // The PORT to use for the Nitro subprocess @@ -61,7 +61,7 @@ async function updateNvidiaDriverInfo(): Promise { (error, stdout) => { let data; try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8")); + data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); } catch (error) { data = DEFALT_SETTINGS; } @@ -109,7 +109,7 @@ function updateCudaExistence() { let data; try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8")); + data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); } catch (error) { data = DEFALT_SETTINGS; } @@ -127,7 +127,7 @@ async function updateGpuInfo(): Promise { (error, stdout) => { let data; try { - data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8")); + data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); } catch (error) { data = DEFALT_SETTINGS; } @@ -376,7 +376,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise { let cudaVisibleDevices = ""; let binaryName; if (process.platform === "win32") { - let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8")); + let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); if (nvida_info["run_mode"] === "cpu") { binaryFolder = path.join(binaryFolder, "win-cpu"); } else { @@ -392,7 +392,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise { } binaryName = "nitro"; } else { - let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8")); + let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8")); if (nvida_info["run_mode"] === "cpu") { binaryFolder = path.join(binaryFolder, "linux-cpu"); } else { @@ -440,11 +440,10 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise { */ function getResourcesInfo(): Promise { return new Promise(async (resolve) => { - const cpu = await si.cpu(); - // const mem = await si.mem(); - + const cpu = await osUtils.cpuCount(); + console.log("cpu: ", cpu); const response: ResourcesInfo = { - numCpuPhysicalCore: cpu.physicalCores, + numCpuPhysicalCore: cpu, memAvailable: 0, }; resolve(response); diff --git a/extensions/monitoring-extension/package.json b/extensions/monitoring-extension/package.json index 8ce172007..9935e536e 100644 --- a/extensions/monitoring-extension/package.json +++ b/extensions/monitoring-extension/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@janhq/core": "file:../../core", - "systeminformation": "^5.21.8", + "node-os-utils": "^1.3.7", "ts-loader": "^9.5.0" }, "files": [ @@ -26,6 +26,6 @@ "README.md" ], "bundleDependencies": [ - "systeminformation" + "node-os-utils" ] } diff --git a/extensions/monitoring-extension/src/module.ts b/extensions/monitoring-extension/src/module.ts index cc10ea4f9..310e7359c 100644 --- a/extensions/monitoring-extension/src/module.ts +++ b/extensions/monitoring-extension/src/module.ts @@ -1,22 +1,32 @@ -const si = require("systeminformation"); +const os = require("os"); +const nodeOsUtils = require("node-os-utils"); -const getResourcesInfo = async () => - new Promise(async (resolve) => { - const cpu = await si.cpu(); - const mem = await si.mem(); - // const gpu = await si.graphics(); - const response = { - cpu, - mem, - // gpu, - }; - resolve(response); +const getResourcesInfo = () => + new Promise((resolve) => { + nodeOsUtils.mem.used() + .then(ramUsedInfo => { + const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024; + const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024; + const response = { + mem: { + totalMemory, + usedMemory, + }, + }; + resolve(response); + }) }); -const getCurrentLoad = async () => - new Promise(async (resolve) => { - const currentLoad = await si.currentLoad(); - resolve(currentLoad); +const getCurrentLoad = () => + new Promise((resolve) => { + nodeOsUtils.cpu.usage().then(cpuPercentage =>{ + const response = { + cpu: { + usage: cpuPercentage, + }, + }; + resolve(response); + }); }); module.exports = { diff --git a/package.json b/package.json index e4a1cae89..4b9492e3d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "scripts": { "lint": "yarn workspace jan lint && yarn workspace jan-web lint", "test": "yarn workspace jan test:e2e", - "dev:electron": "cpx \"models/**\" \"electron/models/\" && yarn workspace jan dev", + "copy:assets": "cpx \"models/**\" \"electron/models/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"", + "dev:electron": "yarn copy:assets && yarn workspace jan dev", "dev:web": "yarn workspace jan-web dev", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", "test-local": "yarn lint && yarn build:test && yarn test", @@ -34,15 +35,15 @@ "build:server": "cd server && yarn install && yarn run build", "build:core": "cd core && yarn install && yarn run build", "build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"", - "build:electron": "cpx \"models/**\" \"electron/models/\" && yarn workspace jan build", + "build:electron": "yarn copy:assets && yarn workspace jan build", "build:electron:test": "yarn workspace jan build:test", "build:extensions:windows": "rimraf ./electron/pre-install/*.tgz && powershell -command \"$jobs = Get-ChildItem -Path './extensions' -Directory | ForEach-Object { Start-Job -Name ($_.Name) -ScriptBlock { param($_dir); try { Set-Location $_dir; npm install; npm run build:publish; Write-Output 'Build successful in ' + $_dir } catch { Write-Error 'Error in ' + $_dir; throw } } -ArgumentList $_.FullName }; $jobs | Wait-Job; $jobs | ForEach-Object { Receive-Job -Job $_ -Keep } | ForEach-Object { Write-Host $_ }; $failed = $jobs | Where-Object { $_.State -ne 'Completed' -or $_.ChildJobs[0].JobStateInfo.State -ne 'Completed' }; if ($failed) { Exit 1 }\"", "build:extensions:linux": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", "build:extensions:darwin": "rimraf ./electron/pre-install/*.tgz && find ./extensions -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n 1 -P 4 -I {} sh -c 'cd {} && npm install && npm run build:publish'", "build:extensions": "run-script-os", - "build:test": "yarn build:web && yarn workspace jan build:test", + "build:test": "yarn copy:assets && yarn build:web && yarn workspace jan build:test", "build": "yarn build:web && yarn build:electron", - "build:publish": "cpx \"models/**\" \"electron/models/\" && yarn build:web && yarn workspace jan build:publish" + "build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish" }, "devDependencies": { "concurrently": "^8.2.1", diff --git a/server/index.ts b/server/index.ts index bc031305e..55b4a8d3c 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,60 +1,78 @@ import fastify from "fastify"; import dotenv from "dotenv"; -import { v1Router } from "@janhq/core/node"; +import { log, v1Router } from "@janhq/core/node"; import path from "path"; +import os from "os"; + dotenv.config(); -const JAN_API_HOST = process.env.JAN_API_HOST || "0.0.0.0"; +const JAN_API_HOST = process.env.JAN_API_HOST || "127.0.0.1"; const JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || "1337"); +const serverLogPath = path.join(os.homedir(), "jan", "server.log"); -const server = fastify(); -server.register(require("@fastify/cors"), {}); -server.register(require("@fastify/swagger"), { - mode: "static", - specification: { - path: "./../docs/openapi/jan.yaml", - baseDir: "./../docs/openapi", - }, -}); -server.register(require("@fastify/swagger-ui"), { - routePrefix: "/docs", - baseDir: path.join(__dirname, "../..", "./docs/openapi"), - uiConfig: { - docExpansion: "full", - deepLinking: false, - }, - staticCSP: true, - transformSpecificationClone: true, -}); -server.register( - (childContext, _, done) => { - childContext.register(require("@fastify/static"), { - root: - process.env.EXTENSION_ROOT || - path.join(require("os").homedir(), "jan", "extensions"), - wildcard: false, +let server: any | undefined = undefined; + +export const startServer = async (schemaPath?: string, baseDir?: string) => { + try { + server = fastify({ + logger: { + level: "info", + file: serverLogPath, + }, + }); + await server.register(require("@fastify/cors"), {}); + + await server.register(require("@fastify/swagger"), { + mode: "static", + specification: { + path: schemaPath ?? "./../docs/openapi/jan.yaml", + baseDir: baseDir ?? "./../docs/openapi", + }, }); - done(); - }, - { prefix: "extensions" } -); -server.register(v1Router, { prefix: "/v1" }); - -export const startServer = () => { - server - .listen({ - port: JAN_API_PORT, - host: JAN_API_HOST, - }) - .then(() => { - console.log( - `JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}` - ); + await server.register(require("@fastify/swagger-ui"), { + routePrefix: "/", + baseDir: baseDir ?? path.join(__dirname, "../..", "./docs/openapi"), + uiConfig: { + docExpansion: "full", + deepLinking: false, + }, + staticCSP: false, + transformSpecificationClone: true, }); + + await server.register( + (childContext: any, _: any, done: any) => { + childContext.register(require("@fastify/static"), { + root: + process.env.EXTENSION_ROOT || + path.join(require("os").homedir(), "jan", "extensions"), + wildcard: false, + }); + + done(); + }, + { prefix: "extensions" } + ); + await server.register(v1Router, { prefix: "/v1" }); + await server + .listen({ + port: JAN_API_PORT, + host: JAN_API_HOST, + }) + .then(() => { + log(`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`); + }); + } catch (e) { + log(e); + } }; -export const stopServer = () => { - server.close(); +export const stopServer = async () => { + try { + await server.close(); + } catch (e) { + log(e); + } }; diff --git a/uikit/src/tooltip/index.tsx b/uikit/src/tooltip/index.tsx index 73c0e1e0b..53e61d175 100644 --- a/uikit/src/tooltip/index.tsx +++ b/uikit/src/tooltip/index.tsx @@ -8,6 +8,8 @@ const TooltipProvider = TooltipPrimitive.Provider const Tooltip = TooltipPrimitive.Root +const TooltipPortal = TooltipPrimitive.Portal + const TooltipTrigger = TooltipPrimitive.Trigger const TooltipContent = React.forwardRef< @@ -37,4 +39,5 @@ export { TooltipContent, TooltipProvider, TooltipArrow, + TooltipPortal, } diff --git a/web/containers/CardSidebar/index.tsx b/web/containers/CardSidebar/index.tsx index e3851214b..ac564c2f2 100644 --- a/web/containers/CardSidebar/index.tsx +++ b/web/containers/CardSidebar/index.tsx @@ -1,31 +1,40 @@ import { ReactNode, useState } from 'react' +import { useAtomValue } from 'jotai' import { ChevronDownIcon, MoreVerticalIcon, FolderOpenIcon, Code2Icon, + PencilIcon, } from 'lucide-react' import { twMerge } from 'tailwind-merge' +import { useActiveModel } from '@/hooks/useActiveModel' import { useClickOutside } from '@/hooks/useClickOutside' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' + interface Props { children: ReactNode title: string - onRevealInFinderClick: (type: string) => void - onViewJsonClick: (type: string) => void + onRevealInFinderClick?: (type: string) => void + onViewJsonClick?: (type: string) => void + asChild?: boolean } export default function CardSidebar({ children, title, onRevealInFinderClick, onViewJsonClick, + asChild, }: Props) { const [show, setShow] = useState(true) const [more, setMore] = useState(false) const [menu, setMenu] = useState(null) const [toggle, setToggle] = useState(null) + const { activeModel } = useActiveModel() + const activeThread = useAtomValue(activeThreadAtom) useClickOutside(() => setMore(false), null, [menu, toggle]) @@ -39,68 +48,127 @@ export default function CardSidebar({ return (
- -
setMore(!more)} - > - + {title} +
+ {!asChild && ( +
setMore(!more)} + > + +
+ )} +
+ {more && (
{ - onRevealInFinderClick(title) + onRevealInFinderClick && onRevealInFinderClick(title) setMore(false) }} > - - - {openFolderTitle} - + + <> + {title === 'Model' ? ( +
+ + Show in Finder + + + Opens thread.json. Changes affect this thread only. + +
+ ) : ( + + Show in Finder + + )} +
{ - onViewJsonClick(title) + onViewJsonClick && onViewJsonClick(title) setMore(false) }} > - - - View as JSON - + + <> +
+ + Edit Global Defaults for{' '} + + {activeThread?.assistants[0].model.id} + + + + {title === 'Model' ? ( + <> + Opens {title}.json. +  Changes affect all new assistants and threads. + + ) : ( + <> + Opens {title}.json. +  Changes affect all new threads. + + )} + +
+
)}
{show && ( -
+
{children}
)} diff --git a/web/containers/Checkbox/index.tsx b/web/containers/Checkbox/index.tsx index de18ca052..db331e2f7 100644 --- a/web/containers/Checkbox/index.tsx +++ b/web/containers/Checkbox/index.tsx @@ -1,32 +1,79 @@ import React from 'react' -import { Switch } from '@janhq/uikit' +import { + Switch, + Tooltip, + TooltipArrow, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from '@janhq/uikit' -import { useAtomValue } from 'jotai' +import { useAtomValue, useSetAtom } from 'jotai' +import { InfoIcon } from 'lucide-react' +import { useActiveModel } from '@/hooks/useActiveModel' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' -import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom' +import { getConfigurationsData } from '@/utils/componentSettings' +import { toSettingParams } from '@/utils/model_param' + +import { + engineParamsUpdateAtom, + getActiveThreadIdAtom, + getActiveThreadModelParamsAtom, +} from '@/helpers/atoms/Thread.atom' type Props = { name: string title: string + description: string checked: boolean } -const Checkbox: React.FC = ({ name, title, checked }) => { +const Checkbox: React.FC = ({ name, title, checked, description }) => { const { updateModelParameter } = useUpdateModelParameters() const threadId = useAtomValue(getActiveThreadIdAtom) + const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) + + const modelSettingParams = toSettingParams(activeModelParams) + + const engineParams = getConfigurationsData(modelSettingParams) + + const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom) + + const { stopModel } = useActiveModel() + const onCheckedChange = (checked: boolean) => { if (!threadId) return - + if (engineParams.some((x) => x.name.includes(name))) { + setEngineParamsUpdate(true) + stopModel() + } else { + setEngineParamsUpdate(false) + } updateModelParameter(threadId, name, checked) } return (
-

{title}

+
+

+ {title} +

+ + + + + + + {description} + + + + +
) diff --git a/web/containers/DropdownListSidebar/index.tsx b/web/containers/DropdownListSidebar/index.tsx index e40599344..0295f5512 100644 --- a/web/containers/DropdownListSidebar/index.tsx +++ b/web/containers/DropdownListSidebar/index.tsx @@ -26,7 +26,7 @@ import { useMainViewState } from '@/hooks/useMainViewState' import useRecommendedModel from '@/hooks/useRecommendedModel' -import { toGigabytes } from '@/utils/converter' +import { toGibibytes } from '@/utils/converter' import { activeThreadAtom, @@ -130,7 +130,7 @@ export default function DropdownListSidebar() {
{x.name} - {toGigabytes(x.metadata.size)} + {toGibibytes(x.metadata.size)}
diff --git a/web/containers/Layout/BottomBar/SystemItem/index.tsx b/web/containers/Layout/BottomBar/SystemItem/index.tsx index bab545547..dfa8cb0d3 100644 --- a/web/containers/Layout/BottomBar/SystemItem/index.tsx +++ b/web/containers/Layout/BottomBar/SystemItem/index.tsx @@ -9,7 +9,7 @@ export default function SystemItem({ name, value }: Props) { return (

{name}

- {value} + {value}
) } diff --git a/web/containers/Layout/BottomBar/index.tsx b/web/containers/Layout/BottomBar/index.tsx index 6fbac8bec..209d1d95c 100644 --- a/web/containers/Layout/BottomBar/index.tsx +++ b/web/containers/Layout/BottomBar/index.tsx @@ -1,6 +1,15 @@ -import { Badge, Button } from '@janhq/uikit' +import { + Badge, + Button, + Tooltip, + TooltipArrow, + TooltipContent, + TooltipTrigger, +} from '@janhq/uikit' import { useAtomValue } from 'jotai' +import { FaGithub, FaDiscord } from 'react-icons/fa' + import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState' import SystemItem from '@/containers/Layout/BottomBar/SystemItem' @@ -15,7 +24,6 @@ import { MainViewState } from '@/constants/screens' import { useActiveModel } from '@/hooks/useActiveModel' import { useDownloadState } from '@/hooks/useDownloadState' -import { useGetAppVersion } from '@/hooks/useGetAppVersion' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' import useGetSystemResources from '@/hooks/useGetSystemResources' import { useMainViewState } from '@/hooks/useMainViewState' @@ -24,11 +32,23 @@ const BottomBar = () => { const { activeModel, stateModel } = useActiveModel() const { ram, cpu } = useGetSystemResources() const progress = useAtomValue(appDownloadProgress) - const appVersion = useGetAppVersion() const { downloadedModels } = useGetDownloadedModels() const { setMainViewState } = useMainViewState() const { downloadStates } = useDownloadState() + const linksMenu = [ + { + name: 'Discord', + icon: , + link: 'https://discord.gg/FTk2MvZwJH', + }, + { + name: 'Github', + icon: , + link: 'https://github.com/janhq/jan', + }, + ] + return (
@@ -71,12 +91,39 @@ const BottomBar = () => {
-
- - - - Jan v{appVersion?.version ?? ''} - +
+
+ + +
+ {/* VERSION is defined by webpack, please see next.config.js */} + Jan v{VERSION ?? ''} +
+ {linksMenu + .filter((link) => !!link) + .map((link, i) => { + return ( +
+ + + + {link.icon} + + + + {link.name} + + + +
+ ) + })} +
) diff --git a/web/containers/Layout/Ribbon/index.tsx b/web/containers/Layout/Ribbon/index.tsx index fa6d53193..2fa5189cc 100644 --- a/web/containers/Layout/Ribbon/index.tsx +++ b/web/containers/Layout/Ribbon/index.tsx @@ -74,6 +74,7 @@ export default function RibbonNav() { state: MainViewState.Settings, }, ] + return (
diff --git a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx index 976fbf279..c9ccfdfbb 100644 --- a/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx +++ b/web/containers/Layout/TopBar/CommandListDownloadedModel/index.tsx @@ -26,7 +26,7 @@ export default function CommandListDownloadedModel() { const onModelActionClick = (modelId: string) => { if (activeModel && activeModel.id === modelId) { - stopModel(modelId) + stopModel() } else { startModel(modelId) } diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx index 257af7de2..c830c84e3 100644 --- a/web/containers/Layout/TopBar/index.tsx +++ b/web/containers/Layout/TopBar/index.tsx @@ -1,18 +1,31 @@ +import { useState } from 'react' + +import { getUserSpace, joinPath, openFileExplorer } from '@janhq/core' import { useAtomValue, useSetAtom } from 'jotai' -import { PanelLeftIcon, PenSquareIcon, PanelRightIcon } from 'lucide-react' +import { + PanelLeftIcon, + PenSquareIcon, + PanelRightIcon, + MoreVerticalIcon, + FolderOpenIcon, + Code2Icon, +} from 'lucide-react' + +import { twMerge } from 'tailwind-merge' import CommandListDownloadedModel from '@/containers/Layout/TopBar/CommandListDownloadedModel' import CommandSearch from '@/containers/Layout/TopBar/CommandSearch' import { MainViewState } from '@/constants/screens' +import { useClickOutside } from '@/hooks/useClickOutside' import { useCreateNewThread } from '@/hooks/useCreateNewThread' import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants' import { useMainViewState } from '@/hooks/useMainViewState' import { showRightSideBarAtom } from '@/screens/Chat/Sidebar' -import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' +import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom' const TopBar = () => { const activeThread = useAtomValue(activeThreadAtom) @@ -20,6 +33,13 @@ const TopBar = () => { const { requestCreateNewThread } = useCreateNewThread() const { assistants } = useGetAssistants() const setShowRightSideBar = useSetAtom(showRightSideBarAtom) + const showing = useAtomValue(showRightSideBarAtom) + const threadStates = useAtomValue(threadStatesAtom) + const [more, setMore] = useState(false) + const [menu, setMenu] = useState(null) + const [toggle, setToggle] = useState(null) + + useClickOutside(() => setMore(false), null, [menu, toggle]) const titleScreen = (viewStateName: MainViewState) => { switch (viewStateName) { @@ -47,15 +67,45 @@ const TopBar = () => { } } + const onReviewInFinderClick = async () => { + if (!activeThread) return + const activeThreadState = threadStates[activeThread.id] + if (!activeThreadState.isFinishInit) { + alert('Thread is not started yet') + return + } + + const userSpace = await getUserSpace() + let filePath = undefined + filePath = await joinPath(['threads', activeThread.id]) + + if (!filePath) return + const fullPath = await joinPath([userSpace, filePath]) + openFileExplorer(fullPath) + } + + const onViewJsonClick = async () => { + if (!activeThread) return + const activeThreadState = threadStates[activeThread.id] + if (!activeThreadState.isFinishInit) { + alert('Thread is not started yet') + return + } + + const userSpace = await getUserSpace() + let filePath = undefined + filePath = await joinPath(['threads', activeThread.id, 'thread.json']) + if (!filePath) return + const fullPath = await joinPath([userSpace, filePath]) + openFileExplorer(fullPath) + } + return (
{mainViewState === MainViewState.Thread && ( -
- )} -
- {mainViewState === MainViewState.Thread ? ( -
-
+
+
+
{ />
- - {titleScreen(mainViewState)} - +
+
+
+ + {titleScreen(mainViewState)} + +
+
+
{activeThread && ( -
setShowRightSideBar((show) => !show)} - > - +
+ {showing && ( +
+ + Threads Settings + +
setMore(!more)} + > + +
+ + {more && ( +
+
{ + onReviewInFinderClick() + setMore(false) + }} + > + + + Show in Finder + +
+
{ + onViewJsonClick() + setMore(false) + }} + > + +
+ + Edit Threads Settings + + + Opens thread.json. Changes affect this thread + only. + +
+
+
+ )} +
+ )} +
setShowRightSideBar((show) => !show)} + > + +
)}
- ) : ( -
- - {titleScreen(mainViewState)} - -
- )} - - -
+
+ )} + + {mainViewState !== MainViewState.Thread && ( +
+ + {titleScreen(mainViewState)} + +
+ )} + +
) } diff --git a/web/containers/Loader/ModelReload.tsx b/web/containers/Loader/ModelReload.tsx new file mode 100644 index 000000000..a432927aa --- /dev/null +++ b/web/containers/Loader/ModelReload.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useState } from 'react' + +import { useActiveModel } from '@/hooks/useActiveModel' + +export default function ModelReload() { + const { stateModel } = useActiveModel() + const [loader, setLoader] = useState(0) + + // This is fake loader please fix this when we have realtime percentage when load model + useEffect(() => { + if (stateModel.loading) { + if (loader === 24) { + setTimeout(() => { + setLoader(loader + 1) + }, 250) + } else if (loader === 50) { + setTimeout(() => { + setLoader(loader + 1) + }, 250) + } else if (loader === 78) { + setTimeout(() => { + setLoader(loader + 1) + }, 250) + } else if (loader === 99) { + setLoader(99) + } else { + setLoader(loader + 1) + } + } else { + setLoader(0) + } + }, [stateModel.loading, loader]) + + if (!stateModel.loading) return null + + return ( +
+
+
+ + Reloading model {stateModel.model} + +
+
+ ) +} diff --git a/web/containers/ModelConfigInput/index.tsx b/web/containers/ModelConfigInput/index.tsx index caa4f7fa3..b7f9e5904 100644 --- a/web/containers/ModelConfigInput/index.tsx +++ b/web/containers/ModelConfigInput/index.tsx @@ -1,14 +1,33 @@ -import { Textarea } from '@janhq/uikit' +import { + Textarea, + Tooltip, + TooltipArrow, + TooltipContent, + TooltipPortal, + TooltipTrigger, +} from '@janhq/uikit' -import { useAtomValue } from 'jotai' +import { useAtomValue, useSetAtom } from 'jotai' +import { InfoIcon } from 'lucide-react' + +import { useActiveModel } from '@/hooks/useActiveModel' import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' -import { getActiveThreadIdAtom } from '@/helpers/atoms/Thread.atom' +import { getConfigurationsData } from '@/utils/componentSettings' + +import { toSettingParams } from '@/utils/model_param' + +import { + engineParamsUpdateAtom, + getActiveThreadIdAtom, + getActiveThreadModelParamsAtom, +} from '@/helpers/atoms/Thread.atom' type Props = { title: string name: string + description: string placeholder: string value: string } @@ -17,20 +36,51 @@ const ModelConfigInput: React.FC = ({ title, name, value, + description, placeholder, }) => { const { updateModelParameter } = useUpdateModelParameters() const threadId = useAtomValue(getActiveThreadIdAtom) + const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom) + + const modelSettingParams = toSettingParams(activeModelParams) + + const engineParams = getConfigurationsData(modelSettingParams) + + const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom) + + const { stopModel } = useActiveModel() + const onValueChanged = (e: React.ChangeEvent) => { if (!threadId) return - + if (engineParams.some((x) => x.name.includes(name))) { + setEngineParamsUpdate(true) + stopModel() + } else { + setEngineParamsUpdate(false) + } updateModelParameter(threadId, name, e.target.value) } return (
-

{title}

+
+

+ {title} +

+ + + + + + + {description} + + + + +
Version Type Windows MacOS Linux
Stable (Recommended) @@ -67,11 +67,30 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
Experimental (Nighlty Build) - - Github action artifactory +
Experimental (Nightly Build) + + + jan.exe + + + + + Intel + + + + + M1/M2 + + + + + jan.deb