Merge branch 'main' into docs/add-guides
This commit is contained in:
commit
30bb911628
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@ -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
|
||||
201
.github/workflows/jan-electron-build-nightly.yml
vendored
201
.github/workflows/jan-electron-build-nightly.yml
vendored
@ -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|<a href='https://github.com/janhq/jan/actions/runs/.*'>|<a href='https://github.com/janhq/jan/actions/runs/${GITHUB_RUN_ID}'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-win-x64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-mac-x64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-mac-arm64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-linux-amd64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb'>|" 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
|
||||
@ -365,10 +347,20 @@ 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: "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|<a href='https://github.com/janhq/jan/actions/runs/.*'>|<a href='https://github.com/janhq/jan/actions/runs/${GITHUB_RUN_ID}'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-win-x64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-win-x64-${{ needs.get-update-version.outputs.new_version }}.exe'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-mac-x64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-mac-arm64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg'>|" README.md
|
||||
sed -i "s|<a href='https://delta.jan.ai/.*/jan-linux-amd64-.*'>|<a href='https://delta.jan.ai/${{ needs.get-update-version.outputs.new_version }}/jan-linux-amd64-${{ needs.get-update-version.outputs.new_version }}.deb'>|" README.md
|
||||
git config --global user.email "service@jan.ai"
|
||||
git config --global user.name "Service Account"
|
||||
git add README.md
|
||||
|
||||
6
.github/workflows/jan-electron-build.yml
vendored
6
.github/workflows/jan-electron-build.yml
vendored
@ -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 }}
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ build
|
||||
.DS_Store
|
||||
electron/renderer
|
||||
electron/models
|
||||
electron/docs
|
||||
package-lock.json
|
||||
|
||||
*.log
|
||||
|
||||
10
Makefile
10
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
|
||||
|
||||
83
README.md
83
README.md
@ -34,13 +34,13 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
## Download
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Version Type</b></td>
|
||||
<td style="text-align:center"><b>Windows</b></td>
|
||||
<td colspan="2" style="text-align:center"><b>MacOS</b></td>
|
||||
<td style="text-align:center"><b>Linux</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.3/jan-win-x64-0.4.3.exe'>
|
||||
@ -68,10 +68,29 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Experimental (Nighlty Build)</b></td>
|
||||
<td style="text-align:center" colspan="4">
|
||||
<a href='https://github.com/janhq/jan/actions/runs/7372465396'>
|
||||
<b>Github action artifactory</b>
|
||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/0.4.3-118/jan-win-x64-0.4.3-118.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/0.4.3-118/jan-mac-x64-0.4.3-118.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/0.4.3-118/jan-mac-arm64-0.4.3-118.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/0.4.3-118/jan-linux-amd64-0.4.3-118.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@ -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 <PID>
|
||||
```
|
||||
|
||||
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
|
||||
@ -156,7 +176,7 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
||||
|
||||
2. **Run development and use Jan Desktop**
|
||||
|
||||
```
|
||||
```bash
|
||||
make dev
|
||||
```
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -84,7 +84,6 @@ export function persistExtensions() {
|
||||
writeFileSync(
|
||||
ExtensionManager.instance.getExtensionsFile(),
|
||||
JSON.stringify(persistData),
|
||||
"utf8"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -5,3 +5,4 @@ export * from './extension/store'
|
||||
export * from './download'
|
||||
export * from './module'
|
||||
export * from './api'
|
||||
export * from './log'
|
||||
|
||||
18
core/src/node/log.ts
Normal file
18
core/src/node/log.ts
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
*/
|
||||
export class ModuleManager {
|
||||
public requiredModules: Record<string, any> = {}
|
||||
public cleaningResource = false
|
||||
|
||||
public static instance: ModuleManager = new ModuleManager()
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Build an Assistant
|
||||
---
|
||||
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
-
|
||||
-
|
||||
25
docs/docs/developer/02-build-assistant/README.mdx
Normal file
25
docs/docs/developer/02-build-assistant/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -1,5 +1,20 @@
|
||||
---
|
||||
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Build an Inference Engine
|
||||
---
|
||||
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
In this tutorial you will learn:
|
||||
|
||||
-
|
||||
-
|
||||
25
docs/docs/developer/03-build-engine/README.mdx
Normal file
25
docs/docs/developer/03-build-engine/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
---
|
||||
title: Package your Extension
|
||||
---
|
||||
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
Packaging, exporting, sharing, publishing an extension to Hub
|
||||
@ -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.
|
||||
:::
|
||||
@ -1,9 +0,0 @@
|
||||
---
|
||||
title: Build an Extension
|
||||
---
|
||||
|
||||
# Overview
|
||||
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
25
docs/docs/developer/04-build-extension/README.mdx
Normal file
25
docs/docs/developer/04-build-extension/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -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).
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Engineering Specs
|
||||
---
|
||||
|
||||
Talk about CoreSDK here
|
||||
24
docs/docs/docs/engineering/README.mdx
Normal file
24
docs/docs/docs/engineering/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
|
||||
Talk about CoreSDK here
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Product Specs
|
||||
---
|
||||
22
docs/docs/docs/product/README.mdx
Normal file
22
docs/docs/docs/product/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList className="DocCardList--no-description" />
|
||||
@ -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)
|
||||
import DocCardList from "@theme/DocCardList";
|
||||
|
||||
<DocCardList />
|
||||
@ -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,
|
||||
|
||||
@ -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,7 +123,7 @@ 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`.
|
||||
|
||||
@ -154,9 +151,9 @@ Edit `model.json` and include the following configurations:
|
||||
"tags": ["7B", "Merged"],
|
||||
"size": 4370000000
|
||||
},
|
||||
"engine": "nitro",
|
||||
// highlight-next-line
|
||||
"state": "ready",
|
||||
"engine": "nitro"
|
||||
"state": "ready"
|
||||
}
|
||||
```
|
||||
|
||||
@ -164,7 +161,7 @@ Edit `model.json` and include the following configurations:
|
||||
|
||||
Restart Jan and navigate to the Hub. Locate your model and click the `Download` button to download the model binary.
|
||||
|
||||

|
||||

|
||||
|
||||
Your model is now ready to use in Jan.
|
||||
|
||||
|
||||
@ -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-<your key here>"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Start the Model
|
||||
|
||||
Restart Jan and navigate to the Hub. Then, select your configured model and start the model.
|
||||
|
||||

|
||||
|
||||
## 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://<server-ip-address>:<port>/v1/chat/completions"
|
||||
"full_url": "https://<server-ip-address>:1337/v1/chat/completions",
|
||||
// highlight-end
|
||||
// Skip api_key if your local server does not require authentication
|
||||
// "api_key": "sk-<your key here>"
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Using Models
|
||||
---
|
||||
21
docs/docs/guides/04-using-models/README.mdx
Normal file
21
docs/docs/guides/04-using-models/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList className="DocCardList" />
|
||||
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 378 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 348 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 372 KiB |
@ -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.
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Using the Local Server
|
||||
---
|
||||
21
docs/docs/guides/05-using-server/README.mdx
Normal file
21
docs/docs/guides/05-using-server/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -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,
|
||||
]
|
||||
---
|
||||
@ -1,5 +1,17 @@
|
||||
---
|
||||
title: Extension Settings
|
||||
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,
|
||||
]
|
||||
---
|
||||
|
||||
TODO: how to configure settings for extensions
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Using Extensions
|
||||
---
|
||||
21
docs/docs/guides/06-using-extensions/README.mdx
Normal file
21
docs/docs/guides/06-using-extensions/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -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,
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
---
|
||||
21
docs/docs/guides/07-troubleshooting/README.mdx
Normal file
21
docs/docs/guides/07-troubleshooting/README.mdx
Normal file
@ -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";
|
||||
|
||||
<DocCardList />
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
3
docs/src/css/custom.css
Normal file
3
docs/src/css/custom.css
Normal file
@ -0,0 +1,3 @@
|
||||
.DocCardList--no-description .card p {
|
||||
display: none;
|
||||
}
|
||||
@ -11,3 +11,5 @@
|
||||
@import "./tweaks/markdown.scss";
|
||||
@import "./tweaks/redocusaurus.scss";
|
||||
@import "./tweaks/sidebar.scss";
|
||||
|
||||
@import "../css/custom.css";
|
||||
@ -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;
|
||||
|
||||
0
electron/docs/openapi/.gitkeep
Normal file
0
electron/docs/openapi/.gitkeep
Normal file
@ -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.
|
||||
|
||||
@ -34,8 +34,18 @@ export function handleDownloaderIPCs() {
|
||||
*/
|
||||
ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => {
|
||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,11 +14,13 @@
|
||||
"build/*.{js,map}",
|
||||
"build/**/*.{js,map}",
|
||||
"pre-install",
|
||||
"models/**/*"
|
||||
"models/**/*",
|
||||
"docs/**/*"
|
||||
],
|
||||
"asarUnpack": [
|
||||
"pre-install",
|
||||
"models"
|
||||
"models",
|
||||
"docs"
|
||||
],
|
||||
"publish": [
|
||||
{
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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<void> {
|
||||
(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<void> {
|
||||
(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<any> {
|
||||
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<any> {
|
||||
}
|
||||
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<any> {
|
||||
*/
|
||||
function getResourcesInfo(): Promise<ResourcesInfo> {
|
||||
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);
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -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 getResourcesInfo = () =>
|
||||
new Promise((resolve) => {
|
||||
nodeOsUtils.mem.used()
|
||||
.then(ramUsedInfo => {
|
||||
const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024;
|
||||
const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024;
|
||||
const response = {
|
||||
cpu,
|
||||
mem,
|
||||
// gpu,
|
||||
mem: {
|
||||
totalMemory,
|
||||
usedMemory,
|
||||
},
|
||||
};
|
||||
resolve(response);
|
||||
})
|
||||
});
|
||||
|
||||
const getCurrentLoad = () =>
|
||||
new Promise((resolve) => {
|
||||
nodeOsUtils.cpu.usage().then(cpuPercentage =>{
|
||||
const response = {
|
||||
cpu: {
|
||||
usage: cpuPercentage,
|
||||
},
|
||||
};
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
const getCurrentLoad = async () =>
|
||||
new Promise(async (resolve) => {
|
||||
const currentLoad = await si.currentLoad();
|
||||
resolve(currentLoad);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -1,34 +1,49 @@
|
||||
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",
|
||||
let server: any | undefined = undefined;
|
||||
|
||||
export const startServer = async (schemaPath?: string, baseDir?: string) => {
|
||||
try {
|
||||
server = fastify({
|
||||
logger: {
|
||||
level: "info",
|
||||
file: serverLogPath,
|
||||
},
|
||||
});
|
||||
server.register(require("@fastify/swagger-ui"), {
|
||||
routePrefix: "/docs",
|
||||
baseDir: path.join(__dirname, "../..", "./docs/openapi"),
|
||||
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",
|
||||
},
|
||||
});
|
||||
|
||||
await server.register(require("@fastify/swagger-ui"), {
|
||||
routePrefix: "/",
|
||||
baseDir: baseDir ?? path.join(__dirname, "../..", "./docs/openapi"),
|
||||
uiConfig: {
|
||||
docExpansion: "full",
|
||||
deepLinking: false,
|
||||
},
|
||||
staticCSP: true,
|
||||
staticCSP: false,
|
||||
transformSpecificationClone: true,
|
||||
});
|
||||
server.register(
|
||||
(childContext, _, done) => {
|
||||
|
||||
await server.register(
|
||||
(childContext: any, _: any, done: any) => {
|
||||
childContext.register(require("@fastify/static"), {
|
||||
root:
|
||||
process.env.EXTENSION_ROOT ||
|
||||
@ -40,21 +55,24 @@ server.register(
|
||||
},
|
||||
{ prefix: "extensions" }
|
||||
);
|
||||
server.register(v1Router, { prefix: "/v1" });
|
||||
|
||||
export const startServer = () => {
|
||||
server
|
||||
await server.register(v1Router, { prefix: "/v1" });
|
||||
await server
|
||||
.listen({
|
||||
port: JAN_API_PORT,
|
||||
host: JAN_API_HOST,
|
||||
})
|
||||
.then(() => {
|
||||
console.log(
|
||||
`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`
|
||||
);
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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<HTMLDivElement | null>(null)
|
||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||
const { activeModel } = useActiveModel()
|
||||
const activeThread = useAtomValue(activeThreadAtom)
|
||||
|
||||
useClickOutside(() => setMore(false), null, [menu, toggle])
|
||||
|
||||
@ -39,18 +48,30 @@ export default function CardSidebar({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
'flex w-full flex-col overflow-hidden rounded-md border border-border bg-zinc-200 dark:bg-zinc-600/10'
|
||||
'flex w-full flex-col border-t border-border bg-zinc-100 dark:bg-zinc-900',
|
||||
asChild ? 'rounded-lg border' : 'border-t'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
'relative flex items-center rounded-t-md ',
|
||||
'relative flex items-center justify-between pl-4',
|
||||
show && 'border-b border-border'
|
||||
)}
|
||||
>
|
||||
<span className="font-bold">{title}</span>
|
||||
<div className="flex">
|
||||
{!asChild && (
|
||||
<div
|
||||
ref={setToggle}
|
||||
className="cursor-pointer rounded-lg bg-zinc-100 p-2 pr-0 dark:bg-zinc-900"
|
||||
onClick={() => setMore(!more)}
|
||||
>
|
||||
<MoreVerticalIcon className="h-5 w-5" />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShow(!show)}
|
||||
className="flex w-full flex-1 items-center space-x-2 bg-zinc-200 px-3 py-2 dark:bg-zinc-600/10"
|
||||
className="flex w-full flex-1 items-center space-x-2 rounded-lg bg-zinc-100 px-3 py-2 dark:bg-zinc-900"
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={twMerge(
|
||||
@ -58,49 +79,96 @@ export default function CardSidebar({
|
||||
show && 'rotate-180'
|
||||
)}
|
||||
/>
|
||||
<span className="font-bold">{title}</span>
|
||||
</button>
|
||||
<div
|
||||
ref={setToggle}
|
||||
className="cursor-pointer rounded-md bg-zinc-200 p-2 dark:bg-zinc-600/10"
|
||||
onClick={() => setMore(!more)}
|
||||
>
|
||||
<MoreVerticalIcon className="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
{more && (
|
||||
<div
|
||||
className="absolute right-0 top-8 z-20 w-52 overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
||||
className="absolute right-4 top-8 z-20 w-72 rounded-lg border border-border bg-background shadow-lg"
|
||||
ref={setMenu}
|
||||
>
|
||||
<div
|
||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
className={twMerge(
|
||||
'flex cursor-pointer space-x-2 px-4 py-2 hover:bg-secondary',
|
||||
title === 'Model' ? 'items-start' : 'items-center'
|
||||
)}
|
||||
onClick={() => {
|
||||
onRevealInFinderClick(title)
|
||||
onRevealInFinderClick && onRevealInFinderClick(title)
|
||||
setMore(false)
|
||||
}}
|
||||
>
|
||||
<FolderOpenIcon size={16} className="text-muted-foreground" />
|
||||
<span className="text-bold text-black dark:text-muted-foreground">
|
||||
{openFolderTitle}
|
||||
<FolderOpenIcon
|
||||
size={16}
|
||||
className={twMerge(
|
||||
'flex-shrink-0 text-muted-foreground',
|
||||
title === 'Model' && 'mt-1'
|
||||
)}
|
||||
/>
|
||||
<>
|
||||
{title === 'Model' ? (
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-black dark:text-muted-foreground">
|
||||
Show in Finder
|
||||
</span>
|
||||
<span className="mt-1 text-muted-foreground">
|
||||
Opens thread.json. Changes affect this thread only.
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-bold text-black dark:text-muted-foreground">
|
||||
Show in Finder
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
<div
|
||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
className="flex cursor-pointer items-start space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
onClick={() => {
|
||||
onViewJsonClick(title)
|
||||
onViewJsonClick && onViewJsonClick(title)
|
||||
setMore(false)
|
||||
}}
|
||||
>
|
||||
<Code2Icon size={16} className="text-muted-foreground" />
|
||||
<span className="text-bold text-black dark:text-muted-foreground">
|
||||
View as JSON
|
||||
<PencilIcon
|
||||
size={16}
|
||||
className="mt-0.5 flex-shrink-0 text-muted-foreground"
|
||||
/>
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<span className="line-clamp-1 font-medium text-black dark:text-muted-foreground">
|
||||
Edit Global Defaults for{' '}
|
||||
<span
|
||||
className="font-bold"
|
||||
title={activeThread?.assistants[0].model.id}
|
||||
>
|
||||
{activeThread?.assistants[0].model.id}
|
||||
</span>
|
||||
</span>
|
||||
<span className="mt-1 text-muted-foreground">
|
||||
{title === 'Model' ? (
|
||||
<>
|
||||
Opens <span className="lowercase">{title}.json.</span>
|
||||
Changes affect all new assistants and threads.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Opens <span className="lowercase">{title}.json.</span>
|
||||
Changes affect all new threads.
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{show && (
|
||||
<div className="flex flex-col gap-2 bg-white p-2 dark:bg-background">
|
||||
<div
|
||||
className={twMerge(
|
||||
'flex flex-col gap-2 bg-white px-2 dark:bg-background',
|
||||
asChild && 'rounded-b-lg'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -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<Props> = ({ name, title, checked }) => {
|
||||
const Checkbox: React.FC<Props> = ({ 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 (
|
||||
<div className="flex justify-between">
|
||||
<p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
|
||||
<div className="mb-1 flex items-center gap-x-2">
|
||||
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||
{title}
|
||||
</p>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="top" className="max-w-[240px]">
|
||||
<span>{description}</span>
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Switch checked={checked} onCheckedChange={onCheckedChange} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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() {
|
||||
<div className="flex w-full justify-between">
|
||||
<span className="line-clamp-1 block">{x.name}</span>
|
||||
<span className="font-bold text-muted-foreground">
|
||||
{toGigabytes(x.metadata.size)}
|
||||
{toGibibytes(x.metadata.size)}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
||||
@ -9,7 +9,7 @@ export default function SystemItem({ name, value }: Props) {
|
||||
return (
|
||||
<div className="flex items-center gap-x-1">
|
||||
<p className="text-xs">{name}</p>
|
||||
<span className="text-xs">{value}</span>
|
||||
<span className="text-xs font-bold">{value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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: <FaDiscord size={20} className="flex-shrink-0" />,
|
||||
link: 'https://discord.gg/FTk2MvZwJH',
|
||||
},
|
||||
{
|
||||
name: 'Github',
|
||||
icon: <FaGithub size={16} className="flex-shrink-0" />,
|
||||
link: 'https://github.com/janhq/jan',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-16 z-20 flex h-12 w-[calc(100%-64px)] items-center justify-between border-t border-border bg-background/80 px-3">
|
||||
<div className="flex flex-shrink-0 items-center gap-x-2">
|
||||
@ -71,12 +91,39 @@ const BottomBar = () => {
|
||||
|
||||
<DownloadingState />
|
||||
</div>
|
||||
<div className="flex gap-x-2">
|
||||
<div className="flex items-center gap-x-4">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<SystemItem name="CPU:" value={`${cpu}%`} />
|
||||
<SystemItem name="Mem:" value={`${ram}%`} />
|
||||
<span className="text-xs font-semibold ">
|
||||
Jan v{appVersion?.version ?? ''}
|
||||
</span>
|
||||
</div>
|
||||
{/* VERSION is defined by webpack, please see next.config.js */}
|
||||
<span className="text-xs">Jan v{VERSION ?? ''}</span>
|
||||
<div className="mt-1 flex items-center gap-x-2">
|
||||
{linksMenu
|
||||
.filter((link) => !!link)
|
||||
.map((link, i) => {
|
||||
return (
|
||||
<div className="relative" key={i}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<a
|
||||
href={link.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="relative flex w-full flex-shrink-0 cursor-pointer items-center justify-center"
|
||||
>
|
||||
{link.icon}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" sideOffset={10}>
|
||||
<span>{link.name}</span>
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -74,6 +74,7 @@ export default function RibbonNav() {
|
||||
state: MainViewState.Settings,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="relative top-12 flex h-[calc(100%-48px)] w-16 flex-shrink-0 flex-col border-r border-border bg-background py-4">
|
||||
<div className="mt-2 flex h-full w-full flex-col items-center justify-between">
|
||||
|
||||
@ -26,7 +26,7 @@ export default function CommandListDownloadedModel() {
|
||||
|
||||
const onModelActionClick = (modelId: string) => {
|
||||
if (activeModel && activeModel.id === modelId) {
|
||||
stopModel(modelId)
|
||||
stopModel()
|
||||
} else {
|
||||
startModel(modelId)
|
||||
}
|
||||
|
||||
@ -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<HTMLDivElement | null>(null)
|
||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(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 (
|
||||
<div className="fixed left-0 top-0 z-50 flex h-12 w-full border-b border-border bg-background/80 backdrop-blur-md">
|
||||
{mainViewState === MainViewState.Thread && (
|
||||
<div className="absolute left-16 h-full w-60 border-r border-border" />
|
||||
)}
|
||||
<div className="relative left-16 flex w-[calc(100%-64px)] items-center justify-between space-x-4 pl-6 pr-2">
|
||||
{mainViewState === MainViewState.Thread ? (
|
||||
<div className="unset-drag flex space-x-8">
|
||||
<div className="flex w-52 justify-between">
|
||||
<div className="relative w-full">
|
||||
<div className="absolute left-16 h-full w-60 border-r border-border">
|
||||
<div className="flex h-full w-full items-center justify-between">
|
||||
<div className="cursor-pointer">
|
||||
<PanelLeftIcon
|
||||
size={20}
|
||||
@ -63,26 +113,100 @@ const TopBar = () => {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer pr-2"
|
||||
className="unset-drag cursor-pointer pr-4"
|
||||
onClick={onCreateConversationClick}
|
||||
>
|
||||
<PenSquareIcon size={20} className="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute left-80 h-full">
|
||||
<div className="flex h-full items-center">
|
||||
<span className="text-sm font-bold">
|
||||
{titleScreen(mainViewState)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={twMerge(
|
||||
'absolute right-0 h-full w-80',
|
||||
showing && 'border-l border-border'
|
||||
)}
|
||||
>
|
||||
{activeThread && (
|
||||
<div className="flex h-full w-52 items-center justify-between px-4">
|
||||
{showing && (
|
||||
<div className="relative flex h-full items-center">
|
||||
<span className="mr-2 text-sm font-bold">
|
||||
Threads Settings
|
||||
</span>
|
||||
<div
|
||||
ref={setToggle}
|
||||
className="unset-drag cursor-pointer rounded-md p-2"
|
||||
onClick={() => setMore(!more)}
|
||||
>
|
||||
<MoreVerticalIcon className="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
{more && (
|
||||
<div
|
||||
className="absolute right-0 top-11 z-20 w-64 overflow-hidden rounded-lg border border-border bg-background shadow-lg"
|
||||
ref={setMenu}
|
||||
>
|
||||
<div
|
||||
className="flex cursor-pointer items-center space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
onClick={() => {
|
||||
onReviewInFinderClick()
|
||||
setMore(false)
|
||||
}}
|
||||
>
|
||||
<FolderOpenIcon
|
||||
size={16}
|
||||
className="text-muted-foreground"
|
||||
/>
|
||||
<span className="font-medium text-black dark:text-muted-foreground">
|
||||
Show in Finder
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex cursor-pointer items-start space-x-2 px-4 py-2 hover:bg-secondary"
|
||||
onClick={() => {
|
||||
onViewJsonClick()
|
||||
setMore(false)
|
||||
}}
|
||||
>
|
||||
<Code2Icon
|
||||
size={16}
|
||||
className="mt-0.5 flex-shrink-0 text-muted-foreground"
|
||||
/>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium text-black dark:text-muted-foreground">
|
||||
Edit Threads Settings
|
||||
</span>
|
||||
<span className="mt-1 text-muted-foreground">
|
||||
Opens thread.json. Changes affect this thread
|
||||
only.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="unset-drag absolute right-4 cursor-pointer"
|
||||
onClick={() => setShowRightSideBar((show) => !show)}
|
||||
>
|
||||
<PanelRightIcon size={20} className="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mainViewState !== MainViewState.Thread && (
|
||||
<div className="relative left-16 flex w-[calc(100%-64px)] items-center justify-between space-x-4 pl-6 pr-2">
|
||||
<span className="text-sm font-bold">
|
||||
{titleScreen(mainViewState)}
|
||||
</span>
|
||||
@ -91,7 +215,6 @@ const TopBar = () => {
|
||||
<CommandSearch />
|
||||
<CommandListDownloadedModel />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
49
web/containers/Loader/ModelReload.tsx
Normal file
49
web/containers/Loader/ModelReload.tsx
Normal file
@ -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 (
|
||||
<div className=" mb-1 mt-2 py-2 text-center">
|
||||
<div className="relative inline-block overflow-hidden rounded-lg border border-neutral-50 bg-blue-50 px-4 py-2 font-semibold text-blue-600 shadow-lg">
|
||||
<div
|
||||
className="absolute left-0 top-0 h-full bg-blue-200"
|
||||
style={{ width: `${loader}%` }}
|
||||
/>
|
||||
<span className="relative z-10">
|
||||
Reloading model {stateModel.model}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -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<Props> = ({
|
||||
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<HTMLTextAreaElement>) => {
|
||||
if (!threadId) return
|
||||
|
||||
if (engineParams.some((x) => x.name.includes(name))) {
|
||||
setEngineParamsUpdate(true)
|
||||
stopModel()
|
||||
} else {
|
||||
setEngineParamsUpdate(false)
|
||||
}
|
||||
updateModelParameter(threadId, name, e.target.value)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
|
||||
<div className="mb-2 flex items-center gap-x-2">
|
||||
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||
{title}
|
||||
</p>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="top" className="max-w-[240px]">
|
||||
<span>{description}</span>
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder={placeholder}
|
||||
onChange={onValueChanged}
|
||||
|
||||
@ -68,10 +68,10 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
||||
setTimeout(async () => {
|
||||
setActiveModel(undefined)
|
||||
setStateModel({ state: 'start', loading: false, model: '' })
|
||||
toaster({
|
||||
title: 'Success!',
|
||||
description: `Model ${model.id} has been stopped.`,
|
||||
})
|
||||
// toaster({
|
||||
// title: 'Success!',
|
||||
// description: `Model ${model.id} has been stopped.`,
|
||||
// })
|
||||
}, 500)
|
||||
}
|
||||
|
||||
|
||||
@ -52,6 +52,7 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
||||
|
||||
window.electronAPI.onFileDownloadError(
|
||||
async (_event: string, state: any) => {
|
||||
if (state.err?.message !== 'aborted')
|
||||
console.error('Download error', state)
|
||||
const modelName = await baseName(state.fileName)
|
||||
const model = modelsRef.current.find(
|
||||
@ -66,7 +67,7 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
||||
if (state && state.fileName) {
|
||||
const modelName = await baseName(state.fileName)
|
||||
const model = modelsRef.current.find(
|
||||
async (model) => modelBinFileName(model) === modelName
|
||||
(model) => modelBinFileName(model) === modelName
|
||||
)
|
||||
if (model) {
|
||||
setDownloadStateSuccess(model.id)
|
||||
|
||||
@ -1,15 +1,34 @@
|
||||
import React from 'react'
|
||||
|
||||
import { Slider, Input } from '@janhq/uikit'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import {
|
||||
Slider,
|
||||
Input,
|
||||
Tooltip,
|
||||
TooltipArrow,
|
||||
TooltipContent,
|
||||
TooltipPortal,
|
||||
TooltipTrigger,
|
||||
} from '@janhq/uikit'
|
||||
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
|
||||
min: number
|
||||
max: number
|
||||
step: number
|
||||
@ -22,20 +41,51 @@ const SliderRightPanel: React.FC<Props> = ({
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
description,
|
||||
value,
|
||||
}) => {
|
||||
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: number[]) => {
|
||||
if (!threadId) return
|
||||
|
||||
if (engineParams.some((x) => x.name.includes(name))) {
|
||||
setEngineParamsUpdate(true)
|
||||
stopModel()
|
||||
} else {
|
||||
setEngineParamsUpdate(false)
|
||||
}
|
||||
updateModelParameter(threadId, name, e[0])
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<p className="mb-2 text-sm font-semibold text-gray-600">{title}</p>
|
||||
<div className="mb-3 flex items-center gap-x-2">
|
||||
<p className="text-sm font-semibold text-zinc-500 dark:text-gray-300">
|
||||
{title}
|
||||
</p>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<InfoIcon size={16} className="flex-shrink-0 dark:text-gray-500" />
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent side="top" className="max-w-[240px]">
|
||||
<span>{description}</span>
|
||||
<TooltipArrow />
|
||||
</TooltipContent>
|
||||
</TooltipPortal>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-4">
|
||||
<div className="relative w-full">
|
||||
<Slider
|
||||
@ -58,7 +108,13 @@ const SliderRightPanel: React.FC<Props> = ({
|
||||
min={min}
|
||||
max={max}
|
||||
value={String(value)}
|
||||
onChange={(e) => onValueChanged([Number(e.target.value)])}
|
||||
onChange={(e) => {
|
||||
if (Number(e.target.value) >= max) {
|
||||
onValueChanged([Number(max)])
|
||||
} else {
|
||||
onValueChanged([Number(e.target.value)])
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,8 @@ import {
|
||||
} from '@janhq/core'
|
||||
import { atom } from 'jotai'
|
||||
|
||||
export const engineParamsUpdateAtom = atom<boolean>(false)
|
||||
|
||||
/**
|
||||
* Stores the current active thread id.
|
||||
*/
|
||||
|
||||
@ -53,10 +53,12 @@ export function useActiveModel() {
|
||||
events.emit(EventName.OnModelInit, model)
|
||||
}
|
||||
|
||||
const stopModel = async (modelId: string) => {
|
||||
const model = downloadedModels.find((e) => e.id === modelId)
|
||||
setStateModel({ state: 'stop', loading: true, model: modelId })
|
||||
events.emit(EventName.OnModelStop, model)
|
||||
const stopModel = async () => {
|
||||
if (activeModel) {
|
||||
setActiveModel(undefined)
|
||||
setStateModel({ state: 'stop', loading: true, model: activeModel.id })
|
||||
events.emit(EventName.OnModelStop, activeModel)
|
||||
}
|
||||
}
|
||||
|
||||
return { activeModel, startModel, stopModel, stateModel }
|
||||
|
||||
@ -33,7 +33,7 @@ const setDownloadStateFailedAtom = atom(null, (get, set, modelId: string) => {
|
||||
const currentState = { ...get(modelDownloadStateAtom) }
|
||||
const state = currentState[modelId]
|
||||
if (!state) {
|
||||
console.error(`Cannot find download state for ${modelId}`)
|
||||
console.debug(`Cannot find download state for ${modelId}`)
|
||||
toaster({
|
||||
title: 'Cancel Download',
|
||||
description: `Model ${modelId} cancel download`,
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useGetAppVersion() {
|
||||
const [version, setVersion] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
getAppVersion()
|
||||
}, [])
|
||||
|
||||
const getAppVersion = () => {
|
||||
window.core?.api?.appVersion().then((version: string | undefined) => {
|
||||
setVersion(version ?? '')
|
||||
})
|
||||
}
|
||||
|
||||
return { version }
|
||||
}
|
||||
@ -32,24 +32,26 @@ export default function useGetSystemResources() {
|
||||
const currentLoadInfor = await monitoring?.getCurrentLoad()
|
||||
|
||||
const ram =
|
||||
(resourceInfor?.mem?.active ?? 0) / (resourceInfor?.mem?.total ?? 1)
|
||||
if (resourceInfor?.mem?.active) setUsedRam(resourceInfor.mem.active)
|
||||
if (resourceInfor?.mem?.total) setTotalRam(resourceInfor.mem.total)
|
||||
(resourceInfor?.mem?.usedMemory ?? 0) /
|
||||
(resourceInfor?.mem?.totalMemory ?? 1)
|
||||
if (resourceInfor?.mem?.usedMemory) setUsedRam(resourceInfor.mem.usedMemory)
|
||||
if (resourceInfor?.mem?.totalMemory)
|
||||
setTotalRam(resourceInfor.mem.totalMemory)
|
||||
|
||||
setRam(Math.round(ram * 100))
|
||||
setCPU(Math.round(currentLoadInfor?.currentLoad ?? 0))
|
||||
setCpuUsage(Math.round(currentLoadInfor?.currentLoad ?? 0))
|
||||
setCPU(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||
setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getSystemResources()
|
||||
|
||||
// Fetch interval - every 5s
|
||||
// Fetch interval - every 0.5s
|
||||
// TODO: Will we really need this?
|
||||
// There is a possibility that this will be removed and replaced by the process event hook?
|
||||
const intervalId = setInterval(() => {
|
||||
getSystemResources()
|
||||
}, 5000)
|
||||
}, 500)
|
||||
|
||||
// clean up interval
|
||||
return () => clearInterval(intervalId)
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
} from '@/helpers/atoms/ChatMessage.atom'
|
||||
import {
|
||||
activeThreadAtom,
|
||||
engineParamsUpdateAtom,
|
||||
getActiveThreadModelParamsAtom,
|
||||
threadStatesAtom,
|
||||
updateThreadAtom,
|
||||
@ -59,6 +60,11 @@ export default function useSendChatMessage() {
|
||||
const updateThreadInitSuccess = useSetAtom(updateThreadInitSuccessAtom)
|
||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||
|
||||
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||
|
||||
const [reloadModel, setReloadModel] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
modelRef.current = activeModel
|
||||
}, [activeModel])
|
||||
@ -135,8 +141,10 @@ export default function useSendChatMessage() {
|
||||
console.error('No active thread')
|
||||
return
|
||||
}
|
||||
const activeThreadState = threadStates[activeThread.id]
|
||||
|
||||
if (engineParamsUpdate) setReloadModel(true)
|
||||
|
||||
const activeThreadState = threadStates[activeThread.id]
|
||||
const runtimeParams = toRuntimeParams(activeModelParams)
|
||||
const settingParams = toSettingParams(activeModelParams)
|
||||
|
||||
@ -256,10 +264,15 @@ export default function useSendChatMessage() {
|
||||
await WaitForModelStarting(modelId)
|
||||
setQueuedMessage(false)
|
||||
}
|
||||
|
||||
events.emit(EventName.OnMessageSent, messageRequest)
|
||||
|
||||
setReloadModel(false)
|
||||
setEngineParamsUpdate(false)
|
||||
}
|
||||
|
||||
return {
|
||||
reloadModel,
|
||||
sendChatMessage,
|
||||
resendChatMessage,
|
||||
queuedMessage,
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.47.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-scroll-to-bottom": "^4.2.0",
|
||||
"react-toastify": "^9.1.3",
|
||||
"sass": "^1.69.4",
|
||||
@ -49,6 +50,7 @@
|
||||
"@types/node": "20.8.10",
|
||||
"@types/react": "18.2.34",
|
||||
"@types/react-dom": "18.2.14",
|
||||
"@types/react-icons": "^3.0.0",
|
||||
"@types/react-scroll-to-bottom": "^4.2.4",
|
||||
"@types/uuid": "^9.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||
|
||||
@ -118,7 +118,6 @@ const ChatBody: React.FC = () => {
|
||||
{messages.map((message, index) => (
|
||||
<div key={message.id}>
|
||||
<ChatItem {...message} key={message.id} />
|
||||
|
||||
{message.status === MessageStatus.Error &&
|
||||
index === messages.length - 1 && (
|
||||
<div
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||
@ -9,7 +10,7 @@ import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
|
||||
|
||||
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
||||
|
||||
const EngineSetting: React.FC = () => {
|
||||
const EngineSetting = () => {
|
||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
|
||||
@ -22,9 +23,9 @@ const EngineSetting: React.FC = () => {
|
||||
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
||||
|
||||
return (
|
||||
<form className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
{settingComponentBuilder(componentData)}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react'
|
||||
|
||||
import { useAtomValue } from 'jotai'
|
||||
@ -11,7 +12,7 @@ import settingComponentBuilder from './settingComponentBuilder'
|
||||
|
||||
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
||||
|
||||
const ModelSetting: React.FC = () => {
|
||||
const ModelSetting = () => {
|
||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
|
||||
@ -24,9 +25,9 @@ const ModelSetting: React.FC = () => {
|
||||
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
||||
|
||||
return (
|
||||
<form className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
{settingComponentBuilder(componentData)}
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
prompt_template: {
|
||||
name: 'prompt_template',
|
||||
title: 'Prompt template',
|
||||
description: 'Prompt template',
|
||||
description: 'The prompt to use for internal configuration.',
|
||||
controllerType: 'input',
|
||||
controllerData: {
|
||||
placeholder: 'Prompt template',
|
||||
@ -14,7 +14,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
stop: {
|
||||
name: 'stop',
|
||||
title: 'Stop',
|
||||
description: 'Stop',
|
||||
description:
|
||||
'Defines specific tokens or phrases at which the model will stop generating further output. ',
|
||||
controllerType: 'input',
|
||||
controllerData: {
|
||||
placeholder: 'Stop',
|
||||
@ -24,7 +25,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
ctx_len: {
|
||||
name: 'ctx_len',
|
||||
title: 'Context Length',
|
||||
description: 'Context Length',
|
||||
description:
|
||||
'The context length for model operations varies; the maximum depends on the specific model used.',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
@ -40,7 +42,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
'The maximum number of tokens the model will generate in a single response.',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
min: 128,
|
||||
max: 4096,
|
||||
step: 128,
|
||||
value: 2048,
|
||||
@ -48,8 +50,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
},
|
||||
ngl: {
|
||||
name: 'ngl',
|
||||
title: 'NGL',
|
||||
description: 'Number of layers in the neural network.',
|
||||
title: 'Number of GPU layers (ngl)',
|
||||
description: 'The number of layers to load onto the GPU for acceleration.',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 1,
|
||||
@ -61,7 +63,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
embedding: {
|
||||
name: 'embedding',
|
||||
title: 'Embedding',
|
||||
description: 'Indicates if embedding layers are used.',
|
||||
description: 'Whether to enable embedding.',
|
||||
controllerType: 'checkbox',
|
||||
controllerData: {
|
||||
checked: true,
|
||||
@ -79,8 +81,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
temperature: {
|
||||
name: 'temperature',
|
||||
title: 'Temperature',
|
||||
description:
|
||||
"Controls randomness in model's responses. Higher values lead to more random responses.",
|
||||
description: 'Controls the randomness of the model’s output.',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
@ -92,7 +93,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
frequency_penalty: {
|
||||
name: 'frequency_penalty',
|
||||
title: 'Frequency Penalty',
|
||||
description: 'Frequency Penalty',
|
||||
description:
|
||||
'Adjusts the likelihood of the model repeating words or phrases in its output. ',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
@ -104,7 +106,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
presence_penalty: {
|
||||
name: 'presence_penalty',
|
||||
title: 'Presence Penalty',
|
||||
description: 'Presence Penalty',
|
||||
description:
|
||||
'Influences the generation of new and varied concepts in the model’s output. ',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
@ -116,7 +119,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
top_p: {
|
||||
name: 'top_p',
|
||||
title: 'Top P',
|
||||
description: 'Top P',
|
||||
description: 'Set probability threshold for more relevant outputs.',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 0,
|
||||
@ -128,10 +131,11 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
||||
n_parallel: {
|
||||
name: 'n_parallel',
|
||||
title: 'N Parallel',
|
||||
description: 'N Parallel',
|
||||
description:
|
||||
'The number of parallel operations. Only set when enable continuous batching. ',
|
||||
controllerType: 'slider',
|
||||
controllerData: {
|
||||
min: 1,
|
||||
min: 0,
|
||||
max: 4,
|
||||
step: 1,
|
||||
value: 1,
|
||||
|
||||
@ -21,6 +21,7 @@ export type InputData = {
|
||||
export type SliderData = {
|
||||
min: number
|
||||
max: number
|
||||
|
||||
step: number
|
||||
value: number
|
||||
}
|
||||
@ -29,8 +30,15 @@ type CheckboxData = {
|
||||
checked: boolean
|
||||
}
|
||||
|
||||
const settingComponentBuilder = (componentData: SettingComponentData[]) => {
|
||||
const components = componentData.map((data) => {
|
||||
const settingComponentBuilder = (
|
||||
componentData: SettingComponentData[],
|
||||
onlyPrompt?: boolean
|
||||
) => {
|
||||
const components = componentData
|
||||
.filter((x) =>
|
||||
onlyPrompt ? x.name === 'prompt_template' : x.name !== 'prompt_template'
|
||||
)
|
||||
.map((data) => {
|
||||
switch (data.controllerType) {
|
||||
case 'slider':
|
||||
const { min, max, step, value } = data.controllerData as SliderData
|
||||
@ -38,6 +46,7 @@ const settingComponentBuilder = (componentData: SettingComponentData[]) => {
|
||||
<Slider
|
||||
key={data.name}
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
@ -53,6 +62,7 @@ const settingComponentBuilder = (componentData: SettingComponentData[]) => {
|
||||
title={data.title}
|
||||
key={data.name}
|
||||
name={data.name}
|
||||
description={data.description}
|
||||
placeholder={placeholder}
|
||||
value={textValue}
|
||||
/>
|
||||
@ -63,6 +73,7 @@ const settingComponentBuilder = (componentData: SettingComponentData[]) => {
|
||||
<Checkbox
|
||||
key={data.name}
|
||||
name={data.name}
|
||||
description={data.description}
|
||||
title={data.title}
|
||||
checked={checked}
|
||||
/>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useContext } from 'react'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React from 'react'
|
||||
|
||||
import { getUserSpace, openFileExplorer, joinPath } from '@janhq/core'
|
||||
|
||||
@ -10,19 +11,21 @@ import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||
import CardSidebar from '@/containers/CardSidebar'
|
||||
|
||||
import DropdownListSidebar, {
|
||||
selectedModelAtom,
|
||||
} from '@/containers/DropdownListSidebar'
|
||||
|
||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
||||
|
||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||
|
||||
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||
import { toSettingParams } from '@/utils/model_param'
|
||||
|
||||
import EngineSetting from '../EngineSetting'
|
||||
import ModelSetting from '../ModelSetting'
|
||||
|
||||
import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
|
||||
|
||||
import {
|
||||
activeThreadAtom,
|
||||
getActiveThreadModelParamsAtom,
|
||||
@ -34,13 +37,14 @@ export const showRightSideBarAtom = atom<boolean>(true)
|
||||
const Sidebar: React.FC = () => {
|
||||
const showing = useAtomValue(showRightSideBarAtom)
|
||||
const activeThread = useAtomValue(activeThreadAtom)
|
||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||
const selectedModel = useAtomValue(selectedModelAtom)
|
||||
const { updateThreadMetadata } = useCreateNewThread()
|
||||
const threadStates = useAtomValue(threadStatesAtom)
|
||||
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
|
||||
|
||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||
const modelSettingParams = toSettingParams(activeModelParams)
|
||||
const threadStates = useAtomValue(threadStatesAtom)
|
||||
|
||||
const modelEngineParams = toSettingParams(activeModelParams)
|
||||
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
|
||||
|
||||
const onReviewInFinderClick = async (type: string) => {
|
||||
if (!activeThread) return
|
||||
@ -119,16 +123,11 @@ const Sidebar: React.FC = () => {
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
'flex flex-col gap-4 p-4 delay-200',
|
||||
'flex flex-col gap-1 delay-200',
|
||||
showing ? 'animate-enter opacity-100' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<CardSidebar
|
||||
title="Thread"
|
||||
onRevealInFinderClick={onReviewInFinderClick}
|
||||
onViewJsonClick={onViewJsonClick}
|
||||
>
|
||||
<div className="flex flex-col space-y-4 p-2">
|
||||
<div className="flex flex-col space-y-4 p-4">
|
||||
<div>
|
||||
<label
|
||||
id="thread-title"
|
||||
@ -151,7 +150,7 @@ const Sidebar: React.FC = () => {
|
||||
<div className="flex flex-col">
|
||||
<label
|
||||
id="thread-title"
|
||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||
>
|
||||
Threads ID
|
||||
</label>
|
||||
@ -160,7 +159,7 @@ const Sidebar: React.FC = () => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
|
||||
<CardSidebar
|
||||
title="Assistant"
|
||||
onRevealInFinderClick={onReviewInFinderClick}
|
||||
@ -176,7 +175,7 @@ const Sidebar: React.FC = () => {
|
||||
<div>
|
||||
<label
|
||||
id="thread-title"
|
||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||
>
|
||||
Instructions
|
||||
</label>
|
||||
@ -198,29 +197,61 @@ const Sidebar: React.FC = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
{experimentalFeatureEnabed && Object.keys(modelSettingParams).length ? (
|
||||
<CardSidebar
|
||||
title="Engine"
|
||||
onRevealInFinderClick={onReviewInFinderClick}
|
||||
onViewJsonClick={onViewJsonClick}
|
||||
{/* Temporary disabled */}
|
||||
{/* <div>
|
||||
<label
|
||||
id="tool-title"
|
||||
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||
>
|
||||
<div className="p-2">
|
||||
<EngineSetting />
|
||||
Tools
|
||||
</label>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="font-medium text-zinc-500 dark:text-gray-300">
|
||||
Retrieval
|
||||
</label>
|
||||
<Switch name="retrieval" />
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</CardSidebar>
|
||||
) : null}
|
||||
<CardSidebar
|
||||
title="Model"
|
||||
onRevealInFinderClick={onReviewInFinderClick}
|
||||
onViewJsonClick={onViewJsonClick}
|
||||
>
|
||||
<div className="p-2">
|
||||
<DropdownListSidebar />
|
||||
<div className="px-2">
|
||||
<div className="mt-4">
|
||||
<DropdownListSidebar />
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<CardSidebar title="Inference Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
<ModelSetting />
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
|
||||
<div className="mt-4">
|
||||
<CardSidebar title="Model Parameters" asChild>
|
||||
<div className="px-2 py-4">
|
||||
{settingComponentBuilder(componentDataEngineSetting, true)}
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
|
||||
<div className="my-4">
|
||||
<CardSidebar
|
||||
title="Engine Parameters"
|
||||
onRevealInFinderClick={onReviewInFinderClick}
|
||||
onViewJsonClick={onViewJsonClick}
|
||||
asChild
|
||||
>
|
||||
<div className="px-2 py-4">
|
||||
<EngineSetting />
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
</div>
|
||||
</CardSidebar>
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@ import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||
|
||||
import ModelReload from '@/containers/Loader/ModelReload'
|
||||
import ModelStart from '@/containers/Loader/ModelStart'
|
||||
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
||||
|
||||
@ -30,8 +31,10 @@ import ThreadList from '@/screens/Chat/ThreadList'
|
||||
import Sidebar, { showRightSideBarAtom } from './Sidebar'
|
||||
|
||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||
|
||||
import {
|
||||
activeThreadAtom,
|
||||
engineParamsUpdateAtom,
|
||||
getActiveThreadIdAtom,
|
||||
waitingToSendMessage,
|
||||
} from '@/helpers/atoms/Thread.atom'
|
||||
@ -48,9 +51,10 @@ const ChatScreen = () => {
|
||||
|
||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
||||
const activeThreadState = useAtomValue(activeThreadStateAtom)
|
||||
const { sendChatMessage, queuedMessage } = useSendChatMessage()
|
||||
const { sendChatMessage, queuedMessage, reloadModel } = useSendChatMessage()
|
||||
const isWaitingForResponse = activeThreadState?.waitingForResponse ?? false
|
||||
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse
|
||||
const isDisabledChatbox =
|
||||
currentPrompt.trim().length === 0 || isWaitingForResponse
|
||||
|
||||
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
||||
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
||||
@ -59,6 +63,7 @@ const ChatScreen = () => {
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
const modelRef = useRef(activeModel)
|
||||
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||
|
||||
useEffect(() => {
|
||||
modelRef.current = activeModel
|
||||
@ -144,18 +149,30 @@ const ChatScreen = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ModelStart />
|
||||
{!engineParamsUpdate && <ModelStart />}
|
||||
|
||||
{queuedMessage && (
|
||||
<div className="my-2 py-2 text-center">
|
||||
<span className="rounded-lg border border-border px-4 py-2 shadow-lg">
|
||||
{reloadModel && (
|
||||
<>
|
||||
<ModelReload />
|
||||
<div className="mb-2 text-center">
|
||||
<span className="text-muted-foreground">
|
||||
Model is reloading to apply new changes.
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{queuedMessage && !reloadModel && (
|
||||
<div className="mb-2 text-center">
|
||||
<span className="text-muted-foreground">
|
||||
Message queued. It can be sent once the model has started
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mx-auto flex w-full flex-shrink-0 items-end justify-center space-x-4 px-8 py-4">
|
||||
<Textarea
|
||||
className="max-h-[400px] resize-none overflow-y-hidden pr-20"
|
||||
className="max-h-[400px] resize-none overflow-y-auto pr-20"
|
||||
style={{ height: '40px' }}
|
||||
ref={textareaRef}
|
||||
onKeyDown={(e: KeyboardEvent<HTMLTextAreaElement>) =>
|
||||
@ -171,7 +188,9 @@ const ChatScreen = () => {
|
||||
{messages[messages.length - 1]?.status !== MessageStatus.Pending ? (
|
||||
<Button
|
||||
size="lg"
|
||||
disabled={disabled || stateModel.loading || !activeThread}
|
||||
disabled={
|
||||
isDisabledChatbox || stateModel.loading || !activeThread
|
||||
}
|
||||
themes="primary"
|
||||
className="min-w-[100px]"
|
||||
onClick={sendChatMessage}
|
||||
|
||||
@ -21,7 +21,7 @@ import { getAssistants } from '@/hooks/useGetAssistants'
|
||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||
|
||||
import { toGigabytes } from '@/utils/converter'
|
||||
import { toGibibytes } from '@/utils/converter'
|
||||
|
||||
type Props = {
|
||||
model: Model
|
||||
@ -99,7 +99,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
||||
</div>
|
||||
<div className="inline-flex items-center space-x-2">
|
||||
<span className="mr-4 font-semibold text-muted-foreground">
|
||||
{toGigabytes(model.metadata.size)}
|
||||
{toGibibytes(model.metadata.size)}
|
||||
</span>
|
||||
{downloadButton}
|
||||
<ChevronDownIcon
|
||||
|
||||
@ -14,8 +14,6 @@ import { useDownloadState } from '@/hooks/useDownloadState'
|
||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||
|
||||
import { toGigabytes } from '@/utils/converter'
|
||||
|
||||
type Props = {
|
||||
model: Model
|
||||
isRecommended: boolean
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user