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
|
||||||
203
.github/workflows/jan-electron-build-nightly.yml
vendored
203
.github/workflows/jan-electron-build-nightly.yml
vendored
@ -15,6 +15,7 @@ jobs:
|
|||||||
uses: unfor19/install-aws-cli-action@v1
|
uses: unfor19/install-aws-cli-action@v1
|
||||||
|
|
||||||
- name: Delete cloudflare-r2 folder using awscli s3api
|
- name: Delete cloudflare-r2 folder using awscli s3api
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
# Get the list of objects in the 'latest' folder
|
# Get the list of objects in the 'latest' folder
|
||||||
@ -34,25 +35,18 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: auto
|
AWS_DEFAULT_REGION: auto
|
||||||
AWS_EC2_METADATA_DISABLED: "true"
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
|
||||||
build-macos:
|
# Job create Update app version based on latest release tag with build number and save to output
|
||||||
runs-on: macos-latest
|
get-update-version:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
needs: delete-cloudflare-r2-folder
|
needs: delete-cloudflare-r2-folder
|
||||||
environment: production
|
environment: production
|
||||||
permissions:
|
outputs:
|
||||||
contents: write
|
new_version: ${{ steps.version_update.outputs.new_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Getting the repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Installing node
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 20
|
|
||||||
|
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
uses: dcarbone/install-jq-action@v2.0.1
|
uses: dcarbone/install-jq-action@v2.0.1
|
||||||
|
|
||||||
|
|
||||||
- name: Update app version based on latest release tag with build number
|
- name: Update app version based on latest release tag with build number
|
||||||
id: version_update
|
id: version_update
|
||||||
run: |
|
run: |
|
||||||
@ -82,11 +76,35 @@ jobs:
|
|||||||
# Remove the 'v' and append the build number to the version
|
# Remove the 'v' and append the build number to the version
|
||||||
NEW_VERSION="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
NEW_VERSION="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
||||||
echo "New version: $NEW_VERSION"
|
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"
|
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
|
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
|
mv /tmp/package.json electron/package.json
|
||||||
@ -119,26 +137,27 @@ jobs:
|
|||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: jan-mac-x64-${{ steps.version_update.outputs.new_version }}
|
name: jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}
|
||||||
path: ./electron/dist/jan-mac-x64-${{ steps.version_update.outputs.new_version }}.dmg
|
path: ./electron/dist/jan-mac-x64-${{ needs.get-update-version.outputs.new_version }}.dmg
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: jan-mac-arm64-${{ steps.version_update.outputs.new_version }}
|
name: jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}
|
||||||
path: ./electron/dist/jan-mac-arm64-${{ steps.version_update.outputs.new_version }}.dmg
|
path: ./electron/dist/jan-mac-arm64-${{ needs.get-update-version.outputs.new_version }}.dmg
|
||||||
|
|
||||||
- name: put-object using awscli s3api
|
- name: put-object using awscli s3api
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
ls -al ./electron/dist
|
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-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-${{ 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-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-${{ 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-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-${{ 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 "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 "${{ 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 "${{ 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 "${{ 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 "${{ 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 "${{ 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 "${{ 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"
|
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:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
||||||
@ -148,7 +167,7 @@ jobs:
|
|||||||
|
|
||||||
build-windows-x64:
|
build-windows-x64:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
needs: delete-cloudflare-r2-folder
|
needs: [delete-cloudflare-r2-folder, get-update-version]
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
@ -167,38 +186,12 @@ jobs:
|
|||||||
id: version_update
|
id: version_update
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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
|
# 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
|
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
|
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
|
mv /tmp/package.json electron/package.json
|
||||||
@ -223,18 +216,19 @@ jobs:
|
|||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
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
|
path: ./electron/dist/*.exe
|
||||||
|
|
||||||
- name: put-object using awscli s3api
|
- name: put-object using awscli s3api
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ls -al ./electron/dist
|
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-${{ 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-${{ 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 "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 "${{ 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 "${{ 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 "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:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
|
||||||
@ -243,7 +237,7 @@ jobs:
|
|||||||
|
|
||||||
build-linux-x64:
|
build-linux-x64:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: delete-cloudflare-r2-folder
|
needs: [delete-cloudflare-r2-folder, get-update-version]
|
||||||
environment: production
|
environment: production
|
||||||
env:
|
env:
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_TOKEN }}
|
||||||
@ -264,37 +258,11 @@ jobs:
|
|||||||
- name: Update app version base on tag
|
- name: Update app version base on tag
|
||||||
id: version_update
|
id: version_update
|
||||||
run: |
|
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
|
# 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
|
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
|
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
|
mv /tmp/package.json electron/package.json
|
||||||
|
|
||||||
@ -307,16 +275,17 @@ jobs:
|
|||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
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
|
path: ./electron/dist/*.deb
|
||||||
|
|
||||||
- name: put-object using awscli s3api
|
- name: put-object using awscli s3api
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
run: |
|
||||||
ls -al ./electron/dist
|
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 "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 "${{ 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 "${{ 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 "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:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
|
||||||
@ -324,7 +293,7 @@ jobs:
|
|||||||
AWS_EC2_METADATA_DISABLED: "true"
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
|
||||||
noti-discord-nightly-and-update-url-readme:
|
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
|
environment: production
|
||||||
if: github.event_name == 'schedule'
|
if: github.event_name == 'schedule'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -335,16 +304,29 @@ jobs:
|
|||||||
fetch-depth: "0"
|
fetch-depth: "0"
|
||||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
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
|
- name: Notify Discord
|
||||||
uses: Ilshidur/action-discord@master
|
uses: Ilshidur/action-discord@master
|
||||||
with:
|
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:
|
env:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|
||||||
- name: Update README.md with artifact URL
|
- name: Update README.md with artifact URL
|
||||||
run: |
|
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.email "service@jan.ai"
|
||||||
git config --global user.name "Service Account"
|
git config --global user.name "Service Account"
|
||||||
git add README.md
|
git add README.md
|
||||||
@ -354,9 +336,9 @@ jobs:
|
|||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
|
|
||||||
noti-discord-manual-and-update-url-readme:
|
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
|
environment: production
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@ -364,11 +346,21 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: "0"
|
fetch-depth: "0"
|
||||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
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
|
- name: Notify Discord
|
||||||
uses: Ilshidur/action-discord@master
|
uses: Ilshidur/action-discord@master
|
||||||
with:
|
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:
|
env:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|
||||||
@ -376,7 +368,10 @@ jobs:
|
|||||||
- name: Update README.md with artifact URL
|
- name: Update README.md with artifact URL
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
run: |
|
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.email "service@jan.ai"
|
||||||
git config --global user.name "Service Account"
|
git config --global user.name "Service Account"
|
||||||
git add README.md
|
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
|
fi
|
||||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/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:
|
env:
|
||||||
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
@ -120,6 +122,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/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
|
jq '.build.win.sign = "./sign.js"' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/package.json
|
mv /tmp/package.json electron/package.json
|
||||||
env:
|
env:
|
||||||
@ -203,6 +207,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
jq --arg version "${VERSION_TAG#v}" '.version = $version' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/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:
|
env:
|
||||||
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
VERSION_TAG: ${{ steps.tag.outputs.tag }}
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ build
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
electron/renderer
|
electron/renderer
|
||||||
electron/models
|
electron/models
|
||||||
|
electron/docs
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
10
Makefile
10
Makefile
@ -52,21 +52,17 @@ build: check-file-counts
|
|||||||
clean:
|
clean:
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist -Recurse -Directory | Remove-Item -Recurse -Force"
|
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%\jan\extensions"
|
||||||
rmdir /s /q "%USERPROFILE%\AppData\Roaming\jan-electron"
|
|
||||||
rmdir /s /q "%USERPROFILE%\AppData\Local\jan*"
|
|
||||||
else ifeq ($(shell uname -s),Linux)
|
else ifeq ($(shell uname -s),Linux)
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||||
rm -rf "~/.config/jan"
|
rm -rf "~/jan/extensions"
|
||||||
rm -rf "~/.config/jan-electron"
|
|
||||||
rm -rf "~/.cache/jan*"
|
rm -rf "~/.cache/jan*"
|
||||||
else
|
else
|
||||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||||
rm -rf ~/Library/Application\ Support/jan
|
rm -rf ~/jan/extensions
|
||||||
rm -rf ~/Library/Application\ Support/jan-electron
|
|
||||||
rm -rf ~/Library/Caches/jan*
|
rm -rf ~/Library/Caches/jan*
|
||||||
endif
|
endif
|
||||||
|
|||||||
101
README.md
101
README.md
@ -34,13 +34,13 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
## Download
|
## Download
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Version Type</b></td>
|
<td style="text-align:center"><b>Version Type</b></td>
|
||||||
<td style="text-align:center"><b>Windows</b></td>
|
<td style="text-align:center"><b>Windows</b></td>
|
||||||
<td colspan="2" style="text-align:center"><b>MacOS</b></td>
|
<td colspan="2" style="text-align:center"><b>MacOS</b></td>
|
||||||
<td style="text-align:center"><b>Linux</b></td>
|
<td style="text-align:center"><b>Linux</b></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.3/jan-win-x64-0.4.3.exe'>
|
<a href='https://github.com/janhq/jan/releases/download/v0.4.3/jan-win-x64-0.4.3.exe'>
|
||||||
@ -67,11 +67,30 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr style="text-align: center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Experimental (Nighlty Build)</b></td>
|
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||||
<td style="text-align:center" colspan="4">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/actions/runs/7372465396'>
|
<a href='https://delta.jan.ai/0.4.3-118/jan-win-x64-0.4.3-118.exe'>
|
||||||
<b>Github action artifactory</b>
|
<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>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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 website](https://jan.ai/)
|
||||||
- [Jan Github](https://github.com/janhq/jan)
|
- [Jan Github](https://github.com/janhq/jan)
|
||||||
- [User Guides](https://jan.ai/docs)
|
- [User Guides](https://jan.ai/guides/)
|
||||||
- [Developer docs](https://jan.ai/docs/extensions/)
|
- [Developer docs](https://jan.ai/developer/)
|
||||||
- [API reference](https://jan.ai/api-reference/)
|
- [API reference](https://jan.ai/api-reference/)
|
||||||
- [Specs](https://jan.ai/specs/)
|
- [Specs](https://jan.ai/docs/)
|
||||||
|
|
||||||
#### Nitro
|
#### Nitro
|
||||||
|
|
||||||
@ -111,18 +130,7 @@ As Jan is in development mode, you might get stuck on a broken build.
|
|||||||
|
|
||||||
To reset your installation:
|
To reset your installation:
|
||||||
|
|
||||||
1. **Remove Jan from your Applications folder and Cache folder**
|
1. Use the following commands to remove any dangling backend processes:
|
||||||
|
|
||||||
```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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ps aux | grep nitro
|
ps aux | grep nitro
|
||||||
@ -134,6 +142,18 @@ To reset your installation:
|
|||||||
kill -9 <PID>
|
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
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file
|
Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) file
|
||||||
@ -148,19 +168,19 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
|||||||
|
|
||||||
1. **Clone the repository and prepare:**
|
1. **Clone the repository and prepare:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/janhq/jan
|
||||||
cd jan
|
cd jan
|
||||||
git checkout -b DESIRED_BRANCH
|
git checkout -b DESIRED_BRANCH
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run development and use Jan Desktop**
|
2. **Run development and use Jan Desktop**
|
||||||
|
|
||||||
```
|
```bash
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will start the development server and open the desktop app.
|
This will start the development server and open the desktop app.
|
||||||
|
|
||||||
### For production build
|
### For production build
|
||||||
|
|
||||||
@ -172,25 +192,6 @@ make build
|
|||||||
|
|
||||||
This will build the app MacOS m1/m2 for production (with code signing already done) and put the result in `dist` folder.
|
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
|
## Acknowledgements
|
||||||
|
|
||||||
Jan builds on top of other open-source projects:
|
Jan builds on top of other open-source projects:
|
||||||
|
|||||||
@ -4,13 +4,14 @@
|
|||||||
*/
|
*/
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
appDataPath = 'appDataPath',
|
appDataPath = 'appDataPath',
|
||||||
appVersion = 'appVersion',
|
|
||||||
openExternalUrl = 'openExternalUrl',
|
openExternalUrl = 'openExternalUrl',
|
||||||
openAppDirectory = 'openAppDirectory',
|
openAppDirectory = 'openAppDirectory',
|
||||||
openFileExplore = 'openFileExplorer',
|
openFileExplore = 'openFileExplorer',
|
||||||
relaunch = 'relaunch',
|
relaunch = 'relaunch',
|
||||||
joinPath = 'joinPath',
|
joinPath = 'joinPath',
|
||||||
baseName = 'baseName',
|
baseName = 'baseName',
|
||||||
|
startServer = 'startServer',
|
||||||
|
stopServer = 'stopServer',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppEvent {
|
export enum AppEvent {
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export class ExtensionManager {
|
|||||||
|
|
||||||
const extensionsJson = join(extDir, "extensions.json");
|
const extensionsJson = join(extDir, "extensions.json");
|
||||||
if (!existsSync(extensionsJson))
|
if (!existsSync(extensionsJson))
|
||||||
writeFileSync(extensionsJson, "{}", "utf8");
|
writeFileSync(extensionsJson, "{}");
|
||||||
|
|
||||||
this.extensionsPath = extDir;
|
this.extensionsPath = extDir;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -84,7 +84,6 @@ export function persistExtensions() {
|
|||||||
writeFileSync(
|
writeFileSync(
|
||||||
ExtensionManager.instance.getExtensionsFile(),
|
ExtensionManager.instance.getExtensionsFile(),
|
||||||
JSON.stringify(persistData),
|
JSON.stringify(persistData),
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,3 +5,4 @@ export * from './extension/store'
|
|||||||
export * from './download'
|
export * from './download'
|
||||||
export * from './module'
|
export * from './module'
|
||||||
export * from './api'
|
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 {
|
export class ModuleManager {
|
||||||
public requiredModules: Record<string, any> = {}
|
public requiredModules: Record<string, any> = {}
|
||||||
|
public cleaningResource = false
|
||||||
|
|
||||||
public static instance: ModuleManager = new ModuleManager()
|
public static instance: ModuleManager = new ModuleManager()
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,23 @@
|
|||||||
---
|
---
|
||||||
title: Your First Assistant
|
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
|
:::caution
|
||||||
This is currently under development.
|
This is currently under development.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
A quickstart on how to build an assistant
|
|
||||||
|
|||||||
@ -1,9 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Anatomy of an Assistant
|
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
|
:::caution
|
||||||
This is currently under development.
|
This is currently under development.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
An overview of assistant.json
|
|
||||||
|
|||||||
@ -1,9 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Package your Assistant
|
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
|
:::caution
|
||||||
This is currently under development.
|
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,9 +1,24 @@
|
|||||||
---
|
---
|
||||||
title: Your First Assistant
|
title: Your First Engine
|
||||||
|
slug: /developer/build-engine/your-first-engine/
|
||||||
|
description: A quick start on how to build your first engine
|
||||||
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
quick start,
|
||||||
|
build engine,
|
||||||
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
:::caution
|
:::caution
|
||||||
This is currently under development.
|
This is currently under development.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
A quickstart on how to integrate tensorrt llm
|
A quickstart on how to integrate tensorrt llm
|
||||||
@ -1,9 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Anatomy of an Engine
|
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
|
:::caution
|
||||||
This is currently under development.
|
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
|
:::caution
|
||||||
This is currently under development.
|
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
|
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
|
:::caution
|
||||||
This is currently under development.
|
This is currently under development.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
A quickstart on tensorrt-llm impl
|
|
||||||
|
|||||||
@ -1,9 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Anatomy of an Extension
|
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
|
:::caution
|
||||||
This is currently under development.
|
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
|
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.
|
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)
|
import DocCardList from "@theme/DocCardList";
|
||||||
- [Upload docs](upload-docs)
|
|
||||||
- [Upload images](upload-images)
|
<DocCardList />
|
||||||
- [Manage chat history](manage-chat-history)
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Install Models from the Hub
|
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:
|
keywords:
|
||||||
[
|
[
|
||||||
Jan AI,
|
Jan AI,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Import Models Manually
|
title: Import Models Manually
|
||||||
slug: /guides/using-models/import-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:
|
keywords:
|
||||||
[
|
[
|
||||||
Jan AI,
|
Jan AI,
|
||||||
@ -13,6 +13,7 @@ keywords:
|
|||||||
no-subscription fee,
|
no-subscription fee,
|
||||||
large language model,
|
large language model,
|
||||||
import-models-manually,
|
import-models-manually,
|
||||||
|
local model,
|
||||||
]
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -24,16 +25,12 @@ This is currently under development.
|
|||||||
import Tabs from "@theme/Tabs";
|
import Tabs from "@theme/Tabs";
|
||||||
import TabItem from "@theme/TabItem";
|
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 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.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
> We are fast shipping a UI to make this easier, but it's a bit manual for now. Apologies.
|
> 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
|
### 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`.
|
Navigate to the `~/jan/models` folder. You can find this folder by going to `App Settings` > `Advanced` > `Open App Directory`.
|
||||||
@ -126,45 +123,45 @@ Edit `model.json` and include the following configurations:
|
|||||||
- Ensure the filename must be `model.json`.
|
- Ensure the filename must be `model.json`.
|
||||||
- Ensure the `id` property matches the folder name you created.
|
- Ensure the `id` property matches the folder name you created.
|
||||||
- Ensure the GGUF filename should match the `id` property exactly.
|
- 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 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`.
|
- Ensure the `state` property is set to `ready`.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{
|
{
|
||||||
// highlight-start
|
// highlight-start
|
||||||
"source_url": "https://huggingface.co/janhq/trinity-v1-GGUF/resolve/main/trinity-v1.Q4_K_M.gguf",
|
"source_url": "https://huggingface.co/janhq/trinity-v1-GGUF/resolve/main/trinity-v1.Q4_K_M.gguf",
|
||||||
"id": "trinity-v1-7b",
|
"id": "trinity-v1-7b",
|
||||||
// highlight-end
|
// highlight-end
|
||||||
"object": "model",
|
"object": "model",
|
||||||
"name": "Trinity-v1 7B Q4",
|
"name": "Trinity-v1 7B Q4",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"description": "Trinity is an experimental model merge of GreenNodeLM & LeoScorpius using the Slerp method. Recommended for daily assistance purposes.",
|
"description": "Trinity is an experimental model merge of GreenNodeLM & LeoScorpius using the Slerp method. Recommended for daily assistance purposes.",
|
||||||
"format": "gguf",
|
"format": "gguf",
|
||||||
"settings": {
|
"settings": {
|
||||||
"ctx_len": 4096,
|
"ctx_len": 4096,
|
||||||
// highlight-next-line
|
|
||||||
"prompt_template": "{system_message}\n### Instruction:\n{prompt}\n### Response:"
|
|
||||||
},
|
|
||||||
"parameters": {
|
|
||||||
"max_tokens": 4096
|
|
||||||
},
|
|
||||||
"metadata": {
|
|
||||||
"author": "Jan",
|
|
||||||
"tags": ["7B", "Merged"],
|
|
||||||
"size": 4370000000
|
|
||||||
},
|
|
||||||
// highlight-next-line
|
// highlight-next-line
|
||||||
"state": "ready",
|
"prompt_template": "{system_message}\n### Instruction:\n{prompt}\n### Response:"
|
||||||
"engine": "nitro"
|
},
|
||||||
}
|
"parameters": {
|
||||||
|
"max_tokens": 4096
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Jan",
|
||||||
|
"tags": ["7B", "Merged"],
|
||||||
|
"size": 4370000000
|
||||||
|
},
|
||||||
|
"engine": "nitro",
|
||||||
|
// highlight-next-line
|
||||||
|
"state": "ready"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Download the Model
|
### 3. Download the Model
|
||||||
|
|
||||||
Restart Jan and navigate to the Hub. Locate your model and click the `Download` button to download the model binary.
|
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.
|
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
|
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:
|
keywords:
|
||||||
[
|
[
|
||||||
Jan AI,
|
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
|
||||||
|
|
||||||
```
|
2. Go to http://localhost:1337/docs for API docs.
|
||||||
curl http://localhost:1337/v1/chat/completions
|
|
||||||
|
|
||||||
```
|
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
|
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
|
title: Extension Settings
|
||||||
---
|
slug: /guides/using-extensions/extension-settings/
|
||||||
|
description: Configure settings for extensions.
|
||||||
TODO: how to configure settings for extensions
|
keywords:
|
||||||
|
[
|
||||||
|
Jan AI,
|
||||||
|
Jan,
|
||||||
|
ChatGPT alternative,
|
||||||
|
local AI,
|
||||||
|
private AI,
|
||||||
|
conversational AI,
|
||||||
|
no-subscription fee,
|
||||||
|
large language model,
|
||||||
|
using-models,
|
||||||
|
]
|
||||||
|
---
|
||||||
@ -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
|
title: Stuck on a Broken Build
|
||||||
slug: /troubleshooting/stuck-on-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:
|
keywords:
|
||||||
[
|
[
|
||||||
Jan AI,
|
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 operate on the basis of trust.
|
||||||
- We expect you to be available and communicative during scheduled meetings or work hours.
|
- We expect you to be available and communicative during scheduled meetings or work hours.
|
||||||
- Turning on video during meetings is encouraged.
|
- 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.
|
- 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.
|
- 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.
|
- 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`.
|
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
|
## 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
|
- `Roadmap Labels` tag large, long-term, & strategic projects that can span multiple teams and multiple sprints
|
||||||
- Example label: `project: Jan has Mobile`
|
- Example label: `roadmap: Jan has Mobile`
|
||||||
- `Projects` contain `epics`
|
- `Roadmaps` contain `epics`
|
||||||
|
|
||||||
[`Epics`](https://github.com/janhq/jan/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+epic%22)
|
[`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
|
- `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` contain `tasks`
|
||||||
- `Epics` should always have 1 owner
|
- `Epics` should always have 1 owner
|
||||||
|
|
||||||
[`Milestones`](https://github.com/janhq/jan/milestones)
|
[`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` track release versions. We use [semantic versioning](https://semver.org/)
|
||||||
- `Milestones` span 1-2 weeks and have deadlines
|
- `Milestones` span ~2 weeks and have deadlines
|
||||||
|
- `Milestones` usually fit within 2 week sprint cycles
|
||||||
|
|
||||||
[`Tasks`](https://github.com/janhq/jan/issues)
|
[`Tasks`](https://github.com/janhq/jan/issues)
|
||||||
|
|
||||||
- Tasks are individual issues (feats, bugs, chores) that can be completed within a few days
|
- 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 are usually named per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)
|
||||||
- Tasks should always have 1 owner
|
- 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
|
- `no status`: issues that need to be triaged (needs an owner, ETA)
|
||||||
- `todo`: issues you plan to tackle within this week
|
- `icebox`: issues you don't plan to tackle yet
|
||||||
|
- `planned`: issues you plan to tackle this week
|
||||||
- `in-progress`: in progress
|
- `in-progress`: in progress
|
||||||
- `in-review`: pending PR or blocked by something
|
- `in-review`: pending PR or blocked by something
|
||||||
- `done`: done
|
- `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;
|
||||||
|
}
|
||||||
@ -2,4 +2,4 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
margin-top: -20px;
|
margin-top: -20px;
|
||||||
}
|
}
|
||||||
@ -11,3 +11,5 @@
|
|||||||
@import "./tweaks/markdown.scss";
|
@import "./tweaks/markdown.scss";
|
||||||
@import "./tweaks/redocusaurus.scss";
|
@import "./tweaks/redocusaurus.scss";
|
||||||
@import "./tweaks/sidebar.scss";
|
@import "./tweaks/sidebar.scss";
|
||||||
|
|
||||||
|
@import "../css/custom.css";
|
||||||
@ -1,6 +1,6 @@
|
|||||||
.theme-doc-markdown {
|
.theme-doc-markdown {
|
||||||
a {
|
a {
|
||||||
@apply text-blue-600 dark:text-blue-400 underline;
|
@apply text-blue-600 dark:text-blue-400;
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style: revert;
|
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 { app, ipcMain, shell, nativeTheme } from 'electron'
|
||||||
import { join, basename } from 'path'
|
import { join, basename } from 'path'
|
||||||
import { WindowManager } from './../managers/window'
|
import { WindowManager } from './../managers/window'
|
||||||
import { userSpacePath } from './../utils/path'
|
import { getResourcePath, userSpacePath } from './../utils/path'
|
||||||
import { AppRoute } from '@janhq/core'
|
import { AppRoute } from '@janhq/core'
|
||||||
import { ExtensionManager, ModuleManager } from '@janhq/core/node'
|
import { ExtensionManager, ModuleManager } from '@janhq/core/node'
|
||||||
|
import { startServer, stopServer } from '@janhq/server'
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
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.
|
* 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.
|
* 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)
|
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.
|
* Relaunches the app in production - reload window in development.
|
||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
|
|||||||
@ -34,8 +34,18 @@ export function handleDownloaderIPCs() {
|
|||||||
*/
|
*/
|
||||||
ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => {
|
ipcMain.handle(DownloadRoute.abortDownload, async (_event, fileName) => {
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
const rq = DownloadManager.instance.networkRequests[fileName]
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
if (rq) {
|
||||||
rq?.abort()
|
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 destination = resolve(userDataPath, fileName)
|
||||||
const rq = request(url)
|
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`
|
const downloadingTempFile = `${destination}.download`
|
||||||
|
|
||||||
progress(rq, {})
|
progress(rq, {})
|
||||||
@ -93,13 +107,11 @@ export function handleDownloaderIPCs() {
|
|||||||
DownloadEvent.onFileDownloadError,
|
DownloadEvent.onFileDownloadError,
|
||||||
{
|
{
|
||||||
fileName,
|
fileName,
|
||||||
err: 'Download cancelled',
|
err: { message: 'aborted' },
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.pipe(createWriteStream(downloadingTempFile))
|
.pipe(createWriteStream(downloadingTempFile))
|
||||||
|
|
||||||
DownloadManager.instance.setRequest(fileName, rq)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,11 +19,7 @@ import { handleAppIPCs } from './handlers/app'
|
|||||||
import { handleAppUpdates } from './handlers/update'
|
import { handleAppUpdates } from './handlers/update'
|
||||||
import { handleFsIPCs } from './handlers/fs'
|
import { handleFsIPCs } from './handlers/fs'
|
||||||
import { migrateExtensions } from './utils/migration'
|
import { migrateExtensions } from './utils/migration'
|
||||||
|
import { dispose } from './utils/disposable'
|
||||||
/**
|
|
||||||
* Server
|
|
||||||
*/
|
|
||||||
import { startServer } from '@janhq/server'
|
|
||||||
|
|
||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
@ -34,7 +30,6 @@ app
|
|||||||
.then(handleIPCs)
|
.then(handleIPCs)
|
||||||
.then(handleAppUpdates)
|
.then(handleAppUpdates)
|
||||||
.then(createMainWindow)
|
.then(createMainWindow)
|
||||||
.then(startServer)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
app.on('activate', () => {
|
app.on('activate', () => {
|
||||||
if (!BrowserWindow.getAllWindows().length) {
|
if (!BrowserWindow.getAllWindows().length) {
|
||||||
@ -43,14 +38,12 @@ app
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.once('window-all-closed', () => {
|
||||||
ModuleManager.instance.clearImportedModules()
|
cleanUpAndQuit()
|
||||||
app.quit()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('quit', () => {
|
app.once('quit', () => {
|
||||||
ModuleManager.instance.clearImportedModules()
|
cleanUpAndQuit()
|
||||||
app.quit()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function createMainWindow() {
|
function createMainWindow() {
|
||||||
@ -75,6 +68,12 @@ function createMainWindow() {
|
|||||||
if (process.platform !== 'darwin') app.quit()
|
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 */
|
/* Enable dev tools for development */
|
||||||
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
if (!app.isPackaged) mainWindow.webContents.openDevTools()
|
||||||
}
|
}
|
||||||
@ -89,3 +88,13 @@ function handleIPCs() {
|
|||||||
handleAppIPCs()
|
handleAppIPCs()
|
||||||
handleFileMangerIPCs()
|
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}",
|
||||||
"build/**/*.{js,map}",
|
"build/**/*.{js,map}",
|
||||||
"pre-install",
|
"pre-install",
|
||||||
"models/**/*"
|
"models/**/*",
|
||||||
|
"docs/**/*"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"pre-install",
|
"pre-install",
|
||||||
"models"
|
"models",
|
||||||
|
"docs"
|
||||||
],
|
],
|
||||||
"publish": [
|
"publish": [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -32,9 +32,9 @@
|
|||||||
"@janhq/core": "file:../../core",
|
"@janhq/core": "file:../../core",
|
||||||
"download-cli": "^1.1.1",
|
"download-cli": "^1.1.1",
|
||||||
"fetch-retry": "^5.0.6",
|
"fetch-retry": "^5.0.6",
|
||||||
|
"os-utils": "^0.0.14",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"systeminformation": "^5.21.20",
|
|
||||||
"tcp-port-used": "^1.0.2",
|
"tcp-port-used": "^1.0.2",
|
||||||
"ts-loader": "^9.5.0",
|
"ts-loader": "^9.5.0",
|
||||||
"ulid": "^2.3.0"
|
"ulid": "^2.3.0"
|
||||||
@ -50,6 +50,6 @@
|
|||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"tcp-port-used",
|
"tcp-port-used",
|
||||||
"fetch-retry",
|
"fetch-retry",
|
||||||
"systeminformation"
|
"os-utils"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const path = require("path");
|
|||||||
const { exec, spawn } = require("child_process");
|
const { exec, spawn } = require("child_process");
|
||||||
const tcpPortUsed = require("tcp-port-used");
|
const tcpPortUsed = require("tcp-port-used");
|
||||||
const fetchRetry = require("fetch-retry")(global.fetch);
|
const fetchRetry = require("fetch-retry")(global.fetch);
|
||||||
const si = require("systeminformation");
|
const osUtils = require("os-utils");
|
||||||
const { readFileSync, writeFileSync, existsSync } = require("fs");
|
const { readFileSync, writeFileSync, existsSync } = require("fs");
|
||||||
|
|
||||||
// The PORT to use for the Nitro subprocess
|
// The PORT to use for the Nitro subprocess
|
||||||
@ -61,7 +61,7 @@ async function updateNvidiaDriverInfo(): Promise<void> {
|
|||||||
(error, stdout) => {
|
(error, stdout) => {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8"));
|
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data = DEFALT_SETTINGS;
|
data = DEFALT_SETTINGS;
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ function updateCudaExistence() {
|
|||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8"));
|
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data = DEFALT_SETTINGS;
|
data = DEFALT_SETTINGS;
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ async function updateGpuInfo(): Promise<void> {
|
|||||||
(error, stdout) => {
|
(error, stdout) => {
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf8"));
|
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
data = DEFALT_SETTINGS;
|
data = DEFALT_SETTINGS;
|
||||||
}
|
}
|
||||||
@ -376,7 +376,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
|||||||
let cudaVisibleDevices = "";
|
let cudaVisibleDevices = "";
|
||||||
let binaryName;
|
let binaryName;
|
||||||
if (process.platform === "win32") {
|
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") {
|
if (nvida_info["run_mode"] === "cpu") {
|
||||||
binaryFolder = path.join(binaryFolder, "win-cpu");
|
binaryFolder = path.join(binaryFolder, "win-cpu");
|
||||||
} else {
|
} else {
|
||||||
@ -392,7 +392,7 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
|||||||
}
|
}
|
||||||
binaryName = "nitro";
|
binaryName = "nitro";
|
||||||
} else {
|
} 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") {
|
if (nvida_info["run_mode"] === "cpu") {
|
||||||
binaryFolder = path.join(binaryFolder, "linux-cpu");
|
binaryFolder = path.join(binaryFolder, "linux-cpu");
|
||||||
} else {
|
} else {
|
||||||
@ -440,11 +440,10 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
|||||||
*/
|
*/
|
||||||
function getResourcesInfo(): Promise<ResourcesInfo> {
|
function getResourcesInfo(): Promise<ResourcesInfo> {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
const cpu = await si.cpu();
|
const cpu = await osUtils.cpuCount();
|
||||||
// const mem = await si.mem();
|
console.log("cpu: ", cpu);
|
||||||
|
|
||||||
const response: ResourcesInfo = {
|
const response: ResourcesInfo = {
|
||||||
numCpuPhysicalCore: cpu.physicalCores,
|
numCpuPhysicalCore: cpu,
|
||||||
memAvailable: 0,
|
memAvailable: 0,
|
||||||
};
|
};
|
||||||
resolve(response);
|
resolve(response);
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "file:../../core",
|
"@janhq/core": "file:../../core",
|
||||||
"systeminformation": "^5.21.8",
|
"node-os-utils": "^1.3.7",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
@ -26,6 +26,6 @@
|
|||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"bundleDependencies": [
|
"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 () =>
|
const getResourcesInfo = () =>
|
||||||
new Promise(async (resolve) => {
|
new Promise((resolve) => {
|
||||||
const cpu = await si.cpu();
|
nodeOsUtils.mem.used()
|
||||||
const mem = await si.mem();
|
.then(ramUsedInfo => {
|
||||||
// const gpu = await si.graphics();
|
const totalMemory = ramUsedInfo.totalMemMb * 1024 * 1024;
|
||||||
const response = {
|
const usedMemory = ramUsedInfo.usedMemMb * 1024 * 1024;
|
||||||
cpu,
|
const response = {
|
||||||
mem,
|
mem: {
|
||||||
// gpu,
|
totalMemory,
|
||||||
};
|
usedMemory,
|
||||||
resolve(response);
|
},
|
||||||
|
};
|
||||||
|
resolve(response);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const getCurrentLoad = async () =>
|
const getCurrentLoad = () =>
|
||||||
new Promise(async (resolve) => {
|
new Promise((resolve) => {
|
||||||
const currentLoad = await si.currentLoad();
|
nodeOsUtils.cpu.usage().then(cpuPercentage =>{
|
||||||
resolve(currentLoad);
|
const response = {
|
||||||
|
cpu: {
|
||||||
|
usage: cpuPercentage,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@ -25,7 +25,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
||||||
"test": "yarn workspace jan test:e2e",
|
"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:web": "yarn workspace jan-web dev",
|
||||||
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
||||||
"test-local": "yarn lint && yarn build:test && yarn test",
|
"test-local": "yarn lint && yarn build:test && yarn test",
|
||||||
@ -34,15 +35,15 @@
|
|||||||
"build:server": "cd server && yarn install && yarn run build",
|
"build:server": "cd server && yarn install && yarn run build",
|
||||||
"build:core": "cd core && 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: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: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: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: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: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: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": "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": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"concurrently": "^8.2.1",
|
||||||
|
|||||||
110
server/index.ts
110
server/index.ts
@ -1,60 +1,78 @@
|
|||||||
import fastify from "fastify";
|
import fastify from "fastify";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { v1Router } from "@janhq/core/node";
|
import { log, v1Router } from "@janhq/core/node";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
import os from "os";
|
||||||
|
|
||||||
dotenv.config();
|
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 JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || "1337");
|
||||||
|
const serverLogPath = path.join(os.homedir(), "jan", "server.log");
|
||||||
|
|
||||||
const server = fastify();
|
let server: any | undefined = undefined;
|
||||||
server.register(require("@fastify/cors"), {});
|
|
||||||
server.register(require("@fastify/swagger"), {
|
export const startServer = async (schemaPath?: string, baseDir?: string) => {
|
||||||
mode: "static",
|
try {
|
||||||
specification: {
|
server = fastify({
|
||||||
path: "./../docs/openapi/jan.yaml",
|
logger: {
|
||||||
baseDir: "./../docs/openapi",
|
level: "info",
|
||||||
},
|
file: serverLogPath,
|
||||||
});
|
},
|
||||||
server.register(require("@fastify/swagger-ui"), {
|
});
|
||||||
routePrefix: "/docs",
|
await server.register(require("@fastify/cors"), {});
|
||||||
baseDir: path.join(__dirname, "../..", "./docs/openapi"),
|
|
||||||
uiConfig: {
|
await server.register(require("@fastify/swagger"), {
|
||||||
docExpansion: "full",
|
mode: "static",
|
||||||
deepLinking: false,
|
specification: {
|
||||||
},
|
path: schemaPath ?? "./../docs/openapi/jan.yaml",
|
||||||
staticCSP: true,
|
baseDir: baseDir ?? "./../docs/openapi",
|
||||||
transformSpecificationClone: true,
|
},
|
||||||
});
|
|
||||||
server.register(
|
|
||||||
(childContext, _, done) => {
|
|
||||||
childContext.register(require("@fastify/static"), {
|
|
||||||
root:
|
|
||||||
process.env.EXTENSION_ROOT ||
|
|
||||||
path.join(require("os").homedir(), "jan", "extensions"),
|
|
||||||
wildcard: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
done();
|
await server.register(require("@fastify/swagger-ui"), {
|
||||||
},
|
routePrefix: "/",
|
||||||
{ prefix: "extensions" }
|
baseDir: baseDir ?? path.join(__dirname, "../..", "./docs/openapi"),
|
||||||
);
|
uiConfig: {
|
||||||
server.register(v1Router, { prefix: "/v1" });
|
docExpansion: "full",
|
||||||
|
deepLinking: false,
|
||||||
export const startServer = () => {
|
},
|
||||||
server
|
staticCSP: false,
|
||||||
.listen({
|
transformSpecificationClone: true,
|
||||||
port: JAN_API_PORT,
|
|
||||||
host: JAN_API_HOST,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
console.log(
|
|
||||||
`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.register(
|
||||||
|
(childContext: any, _: any, done: any) => {
|
||||||
|
childContext.register(require("@fastify/static"), {
|
||||||
|
root:
|
||||||
|
process.env.EXTENSION_ROOT ||
|
||||||
|
path.join(require("os").homedir(), "jan", "extensions"),
|
||||||
|
wildcard: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
{ prefix: "extensions" }
|
||||||
|
);
|
||||||
|
await server.register(v1Router, { prefix: "/v1" });
|
||||||
|
await server
|
||||||
|
.listen({
|
||||||
|
port: JAN_API_PORT,
|
||||||
|
host: JAN_API_HOST,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
log(`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stopServer = () => {
|
export const stopServer = async () => {
|
||||||
server.close();
|
try {
|
||||||
|
await server.close();
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,6 +8,8 @@ const TooltipProvider = TooltipPrimitive.Provider
|
|||||||
|
|
||||||
const Tooltip = TooltipPrimitive.Root
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipPortal = TooltipPrimitive.Portal
|
||||||
|
|
||||||
const TooltipTrigger = TooltipPrimitive.Trigger
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
const TooltipContent = React.forwardRef<
|
const TooltipContent = React.forwardRef<
|
||||||
@ -37,4 +39,5 @@ export {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipArrow,
|
TooltipArrow,
|
||||||
|
TooltipPortal,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +1,40 @@
|
|||||||
import { ReactNode, useState } from 'react'
|
import { ReactNode, useState } from 'react'
|
||||||
|
|
||||||
|
import { useAtomValue } from 'jotai'
|
||||||
import {
|
import {
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
Code2Icon,
|
Code2Icon,
|
||||||
|
PencilIcon,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { twMerge } from 'tailwind-merge'
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
import { useClickOutside } from '@/hooks/useClickOutside'
|
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||||
|
|
||||||
|
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
title: string
|
title: string
|
||||||
onRevealInFinderClick: (type: string) => void
|
onRevealInFinderClick?: (type: string) => void
|
||||||
onViewJsonClick: (type: string) => void
|
onViewJsonClick?: (type: string) => void
|
||||||
|
asChild?: boolean
|
||||||
}
|
}
|
||||||
export default function CardSidebar({
|
export default function CardSidebar({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
onRevealInFinderClick,
|
onRevealInFinderClick,
|
||||||
onViewJsonClick,
|
onViewJsonClick,
|
||||||
|
asChild,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [show, setShow] = useState(true)
|
const [show, setShow] = useState(true)
|
||||||
const [more, setMore] = useState(false)
|
const [more, setMore] = useState(false)
|
||||||
const [menu, setMenu] = useState<HTMLDivElement | null>(null)
|
const [menu, setMenu] = useState<HTMLDivElement | null>(null)
|
||||||
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
const [toggle, setToggle] = useState<HTMLDivElement | null>(null)
|
||||||
|
const { activeModel } = useActiveModel()
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
|
||||||
useClickOutside(() => setMore(false), null, [menu, toggle])
|
useClickOutside(() => setMore(false), null, [menu, toggle])
|
||||||
|
|
||||||
@ -39,68 +48,127 @@ export default function CardSidebar({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
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
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
'relative flex items-center rounded-t-md ',
|
'relative flex items-center justify-between pl-4',
|
||||||
show && 'border-b border-border'
|
show && 'border-b border-border'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<button
|
<span className="font-bold">{title}</span>
|
||||||
onClick={() => setShow(!show)}
|
<div className="flex">
|
||||||
className="flex w-full flex-1 items-center space-x-2 bg-zinc-200 px-3 py-2 dark:bg-zinc-600/10"
|
{!asChild && (
|
||||||
>
|
<div
|
||||||
<ChevronDownIcon
|
ref={setToggle}
|
||||||
className={twMerge(
|
className="cursor-pointer rounded-lg bg-zinc-100 p-2 pr-0 dark:bg-zinc-900"
|
||||||
'h-5 w-5 flex-none text-gray-400',
|
onClick={() => setMore(!more)}
|
||||||
show && 'rotate-180'
|
>
|
||||||
)}
|
<MoreVerticalIcon className="h-5 w-5" />
|
||||||
/>
|
</div>
|
||||||
<span className="font-bold">{title}</span>
|
)}
|
||||||
</button>
|
<button
|
||||||
<div
|
onClick={() => setShow(!show)}
|
||||||
ref={setToggle}
|
className="flex w-full flex-1 items-center space-x-2 rounded-lg bg-zinc-100 px-3 py-2 dark:bg-zinc-900"
|
||||||
className="cursor-pointer rounded-md bg-zinc-200 p-2 dark:bg-zinc-600/10"
|
>
|
||||||
onClick={() => setMore(!more)}
|
<ChevronDownIcon
|
||||||
>
|
className={twMerge(
|
||||||
<MoreVerticalIcon className="h-5 w-5" />
|
'h-5 w-5 flex-none text-gray-400',
|
||||||
|
show && 'rotate-180'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{more && (
|
{more && (
|
||||||
<div
|
<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}
|
ref={setMenu}
|
||||||
>
|
>
|
||||||
<div
|
<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={() => {
|
onClick={() => {
|
||||||
onRevealInFinderClick(title)
|
onRevealInFinderClick && onRevealInFinderClick(title)
|
||||||
setMore(false)
|
setMore(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FolderOpenIcon size={16} className="text-muted-foreground" />
|
<FolderOpenIcon
|
||||||
<span className="text-bold text-black dark:text-muted-foreground">
|
size={16}
|
||||||
{openFolderTitle}
|
className={twMerge(
|
||||||
</span>
|
'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>
|
||||||
<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={() => {
|
onClick={() => {
|
||||||
onViewJsonClick(title)
|
onViewJsonClick && onViewJsonClick(title)
|
||||||
setMore(false)
|
setMore(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Code2Icon size={16} className="text-muted-foreground" />
|
<PencilIcon
|
||||||
<span className="text-bold text-black dark:text-muted-foreground">
|
size={16}
|
||||||
View as JSON
|
className="mt-0.5 flex-shrink-0 text-muted-foreground"
|
||||||
</span>
|
/>
|
||||||
|
<>
|
||||||
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{show && (
|
{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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,32 +1,79 @@
|
|||||||
import React from 'react'
|
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 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 = {
|
type Props = {
|
||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
|
description: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Checkbox: React.FC<Props> = ({ name, title, checked }) => {
|
const Checkbox: React.FC<Props> = ({ name, title, checked, description }) => {
|
||||||
const { updateModelParameter } = useUpdateModelParameters()
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
const threadId = useAtomValue(getActiveThreadIdAtom)
|
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) => {
|
const onCheckedChange = (checked: boolean) => {
|
||||||
if (!threadId) return
|
if (!threadId) return
|
||||||
|
if (engineParams.some((x) => x.name.includes(name))) {
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
} else {
|
||||||
|
setEngineParamsUpdate(false)
|
||||||
|
}
|
||||||
updateModelParameter(threadId, name, checked)
|
updateModelParameter(threadId, name, checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between">
|
<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} />
|
<Switch checked={checked} onCheckedChange={onCheckedChange} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { useMainViewState } from '@/hooks/useMainViewState'
|
|||||||
|
|
||||||
import useRecommendedModel from '@/hooks/useRecommendedModel'
|
import useRecommendedModel from '@/hooks/useRecommendedModel'
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
@ -130,7 +130,7 @@ export default function DropdownListSidebar() {
|
|||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
<span className="line-clamp-1 block">{x.name}</span>
|
<span className="line-clamp-1 block">{x.name}</span>
|
||||||
<span className="font-bold text-muted-foreground">
|
<span className="font-bold text-muted-foreground">
|
||||||
{toGigabytes(x.metadata.size)}
|
{toGibibytes(x.metadata.size)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export default function SystemItem({ name, value }: Props) {
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-x-1">
|
<div className="flex items-center gap-x-1">
|
||||||
<p className="text-xs">{name}</p>
|
<p className="text-xs">{name}</p>
|
||||||
<span className="text-xs">{value}</span>
|
<span className="text-xs font-bold">{value}</span>
|
||||||
</div>
|
</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 { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
|
import { FaGithub, FaDiscord } from 'react-icons/fa'
|
||||||
|
|
||||||
import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState'
|
import DownloadingState from '@/containers/Layout/BottomBar/DownloadingState'
|
||||||
|
|
||||||
import SystemItem from '@/containers/Layout/BottomBar/SystemItem'
|
import SystemItem from '@/containers/Layout/BottomBar/SystemItem'
|
||||||
@ -15,7 +24,6 @@ import { MainViewState } from '@/constants/screens'
|
|||||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||||
|
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetAppVersion } from '@/hooks/useGetAppVersion'
|
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
import useGetSystemResources from '@/hooks/useGetSystemResources'
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
@ -24,11 +32,23 @@ const BottomBar = () => {
|
|||||||
const { activeModel, stateModel } = useActiveModel()
|
const { activeModel, stateModel } = useActiveModel()
|
||||||
const { ram, cpu } = useGetSystemResources()
|
const { ram, cpu } = useGetSystemResources()
|
||||||
const progress = useAtomValue(appDownloadProgress)
|
const progress = useAtomValue(appDownloadProgress)
|
||||||
const appVersion = useGetAppVersion()
|
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const { downloadStates } = useDownloadState()
|
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 (
|
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="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">
|
<div className="flex flex-shrink-0 items-center gap-x-2">
|
||||||
@ -71,12 +91,39 @@ const BottomBar = () => {
|
|||||||
|
|
||||||
<DownloadingState />
|
<DownloadingState />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-x-2">
|
<div className="flex items-center gap-x-4">
|
||||||
<SystemItem name="CPU:" value={`${cpu}%`} />
|
<div className="flex items-center gap-x-2">
|
||||||
<SystemItem name="Mem:" value={`${ram}%`} />
|
<SystemItem name="CPU:" value={`${cpu}%`} />
|
||||||
<span className="text-xs font-semibold ">
|
<SystemItem name="Mem:" value={`${ram}%`} />
|
||||||
Jan v{appVersion?.version ?? ''}
|
</div>
|
||||||
</span>
|
{/* 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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -74,6 +74,7 @@ export default function RibbonNav() {
|
|||||||
state: MainViewState.Settings,
|
state: MainViewState.Settings,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
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="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">
|
<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) => {
|
const onModelActionClick = (modelId: string) => {
|
||||||
if (activeModel && activeModel.id === modelId) {
|
if (activeModel && activeModel.id === modelId) {
|
||||||
stopModel(modelId)
|
stopModel()
|
||||||
} else {
|
} else {
|
||||||
startModel(modelId)
|
startModel(modelId)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,31 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { getUserSpace, joinPath, openFileExplorer } from '@janhq/core'
|
||||||
import { useAtomValue, useSetAtom } from 'jotai'
|
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 CommandListDownloadedModel from '@/containers/Layout/TopBar/CommandListDownloadedModel'
|
||||||
import CommandSearch from '@/containers/Layout/TopBar/CommandSearch'
|
import CommandSearch from '@/containers/Layout/TopBar/CommandSearch'
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
|
import { useClickOutside } from '@/hooks/useClickOutside'
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants'
|
import useGetAssistants, { getAssistants } from '@/hooks/useGetAssistants'
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
||||||
|
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Thread.atom'
|
import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const TopBar = () => {
|
const TopBar = () => {
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
@ -20,6 +33,13 @@ const TopBar = () => {
|
|||||||
const { requestCreateNewThread } = useCreateNewThread()
|
const { requestCreateNewThread } = useCreateNewThread()
|
||||||
const { assistants } = useGetAssistants()
|
const { assistants } = useGetAssistants()
|
||||||
const setShowRightSideBar = useSetAtom(showRightSideBarAtom)
|
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) => {
|
const titleScreen = (viewStateName: MainViewState) => {
|
||||||
switch (viewStateName) {
|
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 (
|
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">
|
<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 && (
|
{mainViewState === MainViewState.Thread && (
|
||||||
<div className="absolute left-16 h-full w-60 border-r border-border" />
|
<div className="relative w-full">
|
||||||
)}
|
<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">
|
<div className="flex h-full w-full items-center justify-between">
|
||||||
{mainViewState === MainViewState.Thread ? (
|
|
||||||
<div className="unset-drag flex space-x-8">
|
|
||||||
<div className="flex w-52 justify-between">
|
|
||||||
<div className="cursor-pointer">
|
<div className="cursor-pointer">
|
||||||
<PanelLeftIcon
|
<PanelLeftIcon
|
||||||
size={20}
|
size={20}
|
||||||
@ -63,34 +113,107 @@ const TopBar = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer pr-2"
|
className="unset-drag cursor-pointer pr-4"
|
||||||
onClick={onCreateConversationClick}
|
onClick={onCreateConversationClick}
|
||||||
>
|
>
|
||||||
<PenSquareIcon size={20} className="text-muted-foreground" />
|
<PenSquareIcon size={20} className="text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-bold">
|
</div>
|
||||||
{titleScreen(mainViewState)}
|
<div className="absolute left-80 h-full">
|
||||||
</span>
|
<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 && (
|
{activeThread && (
|
||||||
<div
|
<div className="flex h-full w-52 items-center justify-between px-4">
|
||||||
className="unset-drag absolute right-4 cursor-pointer"
|
{showing && (
|
||||||
onClick={() => setShowRightSideBar((show) => !show)}
|
<div className="relative flex h-full items-center">
|
||||||
>
|
<span className="mr-2 text-sm font-bold">
|
||||||
<PanelRightIcon size={20} className="text-muted-foreground" />
|
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>
|
||||||
) : (
|
</div>
|
||||||
<div>
|
)}
|
||||||
<span className="text-sm font-bold">
|
|
||||||
{titleScreen(mainViewState)}
|
{mainViewState !== MainViewState.Thread && (
|
||||||
</span>
|
<div className="relative left-16 flex w-[calc(100%-64px)] items-center justify-between space-x-4 pl-6 pr-2">
|
||||||
</div>
|
<span className="text-sm font-bold">
|
||||||
)}
|
{titleScreen(mainViewState)}
|
||||||
<CommandSearch />
|
</span>
|
||||||
<CommandListDownloadedModel />
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
<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 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 = {
|
type Props = {
|
||||||
title: string
|
title: string
|
||||||
name: string
|
name: string
|
||||||
|
description: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
@ -17,20 +36,51 @@ const ModelConfigInput: React.FC<Props> = ({
|
|||||||
title,
|
title,
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
|
description,
|
||||||
placeholder,
|
placeholder,
|
||||||
}) => {
|
}) => {
|
||||||
const { updateModelParameter } = useUpdateModelParameters()
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
const threadId = useAtomValue(getActiveThreadIdAtom)
|
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>) => {
|
const onValueChanged = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
if (!threadId) return
|
if (!threadId) return
|
||||||
|
if (engineParams.some((x) => x.name.includes(name))) {
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
} else {
|
||||||
|
setEngineParamsUpdate(false)
|
||||||
|
}
|
||||||
updateModelParameter(threadId, name, e.target.value)
|
updateModelParameter(threadId, name, e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<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
|
<Textarea
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={onValueChanged}
|
onChange={onValueChanged}
|
||||||
|
|||||||
@ -68,10 +68,10 @@ export default function EventHandler({ children }: { children: ReactNode }) {
|
|||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
setActiveModel(undefined)
|
setActiveModel(undefined)
|
||||||
setStateModel({ state: 'start', loading: false, model: '' })
|
setStateModel({ state: 'start', loading: false, model: '' })
|
||||||
toaster({
|
// toaster({
|
||||||
title: 'Success!',
|
// title: 'Success!',
|
||||||
description: `Model ${model.id} has been stopped.`,
|
// description: `Model ${model.id} has been stopped.`,
|
||||||
})
|
// })
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -52,7 +52,8 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
|
|
||||||
window.electronAPI.onFileDownloadError(
|
window.electronAPI.onFileDownloadError(
|
||||||
async (_event: string, state: any) => {
|
async (_event: string, state: any) => {
|
||||||
console.error('Download error', state)
|
if (state.err?.message !== 'aborted')
|
||||||
|
console.error('Download error', state)
|
||||||
const modelName = await baseName(state.fileName)
|
const modelName = await baseName(state.fileName)
|
||||||
const model = modelsRef.current.find(
|
const model = modelsRef.current.find(
|
||||||
(model) => modelBinFileName(model) === modelName
|
(model) => modelBinFileName(model) === modelName
|
||||||
@ -66,7 +67,7 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
if (state && state.fileName) {
|
if (state && state.fileName) {
|
||||||
const modelName = await baseName(state.fileName)
|
const modelName = await baseName(state.fileName)
|
||||||
const model = modelsRef.current.find(
|
const model = modelsRef.current.find(
|
||||||
async (model) => modelBinFileName(model) === modelName
|
(model) => modelBinFileName(model) === modelName
|
||||||
)
|
)
|
||||||
if (model) {
|
if (model) {
|
||||||
setDownloadStateSuccess(model.id)
|
setDownloadStateSuccess(model.id)
|
||||||
|
|||||||
@ -1,15 +1,34 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Slider, Input } from '@janhq/uikit'
|
import {
|
||||||
import { useAtomValue } from 'jotai'
|
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 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 = {
|
type Props = {
|
||||||
name: string
|
name: string
|
||||||
title: string
|
title: string
|
||||||
|
description: string
|
||||||
min: number
|
min: number
|
||||||
max: number
|
max: number
|
||||||
step: number
|
step: number
|
||||||
@ -22,20 +41,51 @@ const SliderRightPanel: React.FC<Props> = ({
|
|||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
step,
|
step,
|
||||||
|
description,
|
||||||
value,
|
value,
|
||||||
}) => {
|
}) => {
|
||||||
const { updateModelParameter } = useUpdateModelParameters()
|
const { updateModelParameter } = useUpdateModelParameters()
|
||||||
const threadId = useAtomValue(getActiveThreadIdAtom)
|
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[]) => {
|
const onValueChanged = (e: number[]) => {
|
||||||
if (!threadId) return
|
if (!threadId) return
|
||||||
|
if (engineParams.some((x) => x.name.includes(name))) {
|
||||||
|
setEngineParamsUpdate(true)
|
||||||
|
stopModel()
|
||||||
|
} else {
|
||||||
|
setEngineParamsUpdate(false)
|
||||||
|
}
|
||||||
updateModelParameter(threadId, name, e[0])
|
updateModelParameter(threadId, name, e[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<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="flex items-center gap-x-4">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<Slider
|
<Slider
|
||||||
@ -58,7 +108,13 @@ const SliderRightPanel: React.FC<Props> = ({
|
|||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
value={String(value)}
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import {
|
|||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { atom } from 'jotai'
|
import { atom } from 'jotai'
|
||||||
|
|
||||||
|
export const engineParamsUpdateAtom = atom<boolean>(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the current active thread id.
|
* Stores the current active thread id.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -53,10 +53,12 @@ export function useActiveModel() {
|
|||||||
events.emit(EventName.OnModelInit, model)
|
events.emit(EventName.OnModelInit, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopModel = async (modelId: string) => {
|
const stopModel = async () => {
|
||||||
const model = downloadedModels.find((e) => e.id === modelId)
|
if (activeModel) {
|
||||||
setStateModel({ state: 'stop', loading: true, model: modelId })
|
setActiveModel(undefined)
|
||||||
events.emit(EventName.OnModelStop, model)
|
setStateModel({ state: 'stop', loading: true, model: activeModel.id })
|
||||||
|
events.emit(EventName.OnModelStop, activeModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { activeModel, startModel, stopModel, stateModel }
|
return { activeModel, startModel, stopModel, stateModel }
|
||||||
|
|||||||
@ -33,7 +33,7 @@ const setDownloadStateFailedAtom = atom(null, (get, set, modelId: string) => {
|
|||||||
const currentState = { ...get(modelDownloadStateAtom) }
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
const state = currentState[modelId]
|
const state = currentState[modelId]
|
||||||
if (!state) {
|
if (!state) {
|
||||||
console.error(`Cannot find download state for ${modelId}`)
|
console.debug(`Cannot find download state for ${modelId}`)
|
||||||
toaster({
|
toaster({
|
||||||
title: 'Cancel Download',
|
title: 'Cancel Download',
|
||||||
description: `Model ${modelId} 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 currentLoadInfor = await monitoring?.getCurrentLoad()
|
||||||
|
|
||||||
const ram =
|
const ram =
|
||||||
(resourceInfor?.mem?.active ?? 0) / (resourceInfor?.mem?.total ?? 1)
|
(resourceInfor?.mem?.usedMemory ?? 0) /
|
||||||
if (resourceInfor?.mem?.active) setUsedRam(resourceInfor.mem.active)
|
(resourceInfor?.mem?.totalMemory ?? 1)
|
||||||
if (resourceInfor?.mem?.total) setTotalRam(resourceInfor.mem.total)
|
if (resourceInfor?.mem?.usedMemory) setUsedRam(resourceInfor.mem.usedMemory)
|
||||||
|
if (resourceInfor?.mem?.totalMemory)
|
||||||
|
setTotalRam(resourceInfor.mem.totalMemory)
|
||||||
|
|
||||||
setRam(Math.round(ram * 100))
|
setRam(Math.round(ram * 100))
|
||||||
setCPU(Math.round(currentLoadInfor?.currentLoad ?? 0))
|
setCPU(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||||
setCpuUsage(Math.round(currentLoadInfor?.currentLoad ?? 0))
|
setCpuUsage(Math.round(currentLoadInfor?.cpu?.usage ?? 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSystemResources()
|
getSystemResources()
|
||||||
|
|
||||||
// Fetch interval - every 5s
|
// Fetch interval - every 0.5s
|
||||||
// TODO: Will we really need this?
|
// TODO: Will we really need this?
|
||||||
// There is a possibility that this will be removed and replaced by the process event hook?
|
// There is a possibility that this will be removed and replaced by the process event hook?
|
||||||
const intervalId = setInterval(() => {
|
const intervalId = setInterval(() => {
|
||||||
getSystemResources()
|
getSystemResources()
|
||||||
}, 5000)
|
}, 500)
|
||||||
|
|
||||||
// clean up interval
|
// clean up interval
|
||||||
return () => clearInterval(intervalId)
|
return () => clearInterval(intervalId)
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
} from '@/helpers/atoms/ChatMessage.atom'
|
} from '@/helpers/atoms/ChatMessage.atom'
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
|
engineParamsUpdateAtom,
|
||||||
getActiveThreadModelParamsAtom,
|
getActiveThreadModelParamsAtom,
|
||||||
threadStatesAtom,
|
threadStatesAtom,
|
||||||
updateThreadAtom,
|
updateThreadAtom,
|
||||||
@ -59,6 +60,11 @@ export default function useSendChatMessage() {
|
|||||||
const updateThreadInitSuccess = useSetAtom(updateThreadInitSuccessAtom)
|
const updateThreadInitSuccess = useSetAtom(updateThreadInitSuccessAtom)
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
|
|
||||||
|
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||||
|
const setEngineParamsUpdate = useSetAtom(engineParamsUpdateAtom)
|
||||||
|
|
||||||
|
const [reloadModel, setReloadModel] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modelRef.current = activeModel
|
modelRef.current = activeModel
|
||||||
}, [activeModel])
|
}, [activeModel])
|
||||||
@ -135,8 +141,10 @@ export default function useSendChatMessage() {
|
|||||||
console.error('No active thread')
|
console.error('No active thread')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const activeThreadState = threadStates[activeThread.id]
|
|
||||||
|
|
||||||
|
if (engineParamsUpdate) setReloadModel(true)
|
||||||
|
|
||||||
|
const activeThreadState = threadStates[activeThread.id]
|
||||||
const runtimeParams = toRuntimeParams(activeModelParams)
|
const runtimeParams = toRuntimeParams(activeModelParams)
|
||||||
const settingParams = toSettingParams(activeModelParams)
|
const settingParams = toSettingParams(activeModelParams)
|
||||||
|
|
||||||
@ -256,10 +264,15 @@ export default function useSendChatMessage() {
|
|||||||
await WaitForModelStarting(modelId)
|
await WaitForModelStarting(modelId)
|
||||||
setQueuedMessage(false)
|
setQueuedMessage(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
events.emit(EventName.OnMessageSent, messageRequest)
|
events.emit(EventName.OnMessageSent, messageRequest)
|
||||||
|
|
||||||
|
setReloadModel(false)
|
||||||
|
setEngineParamsUpdate(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
reloadModel,
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
resendChatMessage,
|
resendChatMessage,
|
||||||
queuedMessage,
|
queuedMessage,
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.47.0",
|
"react-hook-form": "^7.47.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
|
"react-icons": "^4.12.0",
|
||||||
"react-scroll-to-bottom": "^4.2.0",
|
"react-scroll-to-bottom": "^4.2.0",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"sass": "^1.69.4",
|
"sass": "^1.69.4",
|
||||||
@ -49,6 +50,7 @@
|
|||||||
"@types/node": "20.8.10",
|
"@types/node": "20.8.10",
|
||||||
"@types/react": "18.2.34",
|
"@types/react": "18.2.34",
|
||||||
"@types/react-dom": "18.2.14",
|
"@types/react-dom": "18.2.14",
|
||||||
|
"@types/react-icons": "^3.0.0",
|
||||||
"@types/react-scroll-to-bottom": "^4.2.4",
|
"@types/react-scroll-to-bottom": "^4.2.4",
|
||||||
"@types/uuid": "^9.0.6",
|
"@types/uuid": "^9.0.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||||
|
|||||||
@ -118,7 +118,6 @@ const ChatBody: React.FC = () => {
|
|||||||
{messages.map((message, index) => (
|
{messages.map((message, index) => (
|
||||||
<div key={message.id}>
|
<div key={message.id}>
|
||||||
<ChatItem {...message} key={message.id} />
|
<ChatItem {...message} key={message.id} />
|
||||||
|
|
||||||
{message.status === MessageStatus.Error &&
|
{message.status === MessageStatus.Error &&
|
||||||
index === messages.length - 1 && (
|
index === messages.length - 1 && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
import { selectedModelAtom } from '@/containers/DropdownListSidebar'
|
||||||
@ -9,7 +10,7 @@ import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
|
|||||||
|
|
||||||
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const EngineSetting: React.FC = () => {
|
const EngineSetting = () => {
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
|
|
||||||
@ -22,9 +23,9 @@ const EngineSetting: React.FC = () => {
|
|||||||
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{settingComponentBuilder(componentData)}
|
{settingComponentBuilder(componentData)}
|
||||||
</form>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
@ -11,7 +12,7 @@ import settingComponentBuilder from './settingComponentBuilder'
|
|||||||
|
|
||||||
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
import { getActiveThreadModelParamsAtom } from '@/helpers/atoms/Thread.atom'
|
||||||
|
|
||||||
const ModelSetting: React.FC = () => {
|
const ModelSetting = () => {
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
|
|
||||||
@ -24,9 +25,9 @@ const ModelSetting: React.FC = () => {
|
|||||||
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
componentData.sort((a, b) => a.title.localeCompare(b.title))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
{settingComponentBuilder(componentData)}
|
{settingComponentBuilder(componentData)}
|
||||||
</form>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
prompt_template: {
|
prompt_template: {
|
||||||
name: 'prompt_template',
|
name: 'prompt_template',
|
||||||
title: 'Prompt template',
|
title: 'Prompt template',
|
||||||
description: 'Prompt template',
|
description: 'The prompt to use for internal configuration.',
|
||||||
controllerType: 'input',
|
controllerType: 'input',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
placeholder: 'Prompt template',
|
placeholder: 'Prompt template',
|
||||||
@ -14,7 +14,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
stop: {
|
stop: {
|
||||||
name: 'stop',
|
name: 'stop',
|
||||||
title: 'Stop',
|
title: 'Stop',
|
||||||
description: 'Stop',
|
description:
|
||||||
|
'Defines specific tokens or phrases at which the model will stop generating further output. ',
|
||||||
controllerType: 'input',
|
controllerType: 'input',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
placeholder: 'Stop',
|
placeholder: 'Stop',
|
||||||
@ -24,7 +25,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
ctx_len: {
|
ctx_len: {
|
||||||
name: 'ctx_len',
|
name: 'ctx_len',
|
||||||
title: 'Context Length',
|
title: 'Context Length',
|
||||||
description: 'Context Length',
|
description:
|
||||||
|
'The context length for model operations varies; the maximum depends on the specific model used.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
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.',
|
'The maximum number of tokens the model will generate in a single response.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
min: 128,
|
||||||
max: 4096,
|
max: 4096,
|
||||||
step: 128,
|
step: 128,
|
||||||
value: 2048,
|
value: 2048,
|
||||||
@ -48,8 +50,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
},
|
},
|
||||||
ngl: {
|
ngl: {
|
||||||
name: 'ngl',
|
name: 'ngl',
|
||||||
title: 'NGL',
|
title: 'Number of GPU layers (ngl)',
|
||||||
description: 'Number of layers in the neural network.',
|
description: 'The number of layers to load onto the GPU for acceleration.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 1,
|
min: 1,
|
||||||
@ -61,7 +63,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
embedding: {
|
embedding: {
|
||||||
name: 'embedding',
|
name: 'embedding',
|
||||||
title: 'Embedding',
|
title: 'Embedding',
|
||||||
description: 'Indicates if embedding layers are used.',
|
description: 'Whether to enable embedding.',
|
||||||
controllerType: 'checkbox',
|
controllerType: 'checkbox',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
checked: true,
|
checked: true,
|
||||||
@ -79,8 +81,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
temperature: {
|
temperature: {
|
||||||
name: 'temperature',
|
name: 'temperature',
|
||||||
title: 'Temperature',
|
title: 'Temperature',
|
||||||
description:
|
description: 'Controls the randomness of the model’s output.',
|
||||||
"Controls randomness in model's responses. Higher values lead to more random responses.",
|
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
min: 0,
|
||||||
@ -92,7 +93,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
frequency_penalty: {
|
frequency_penalty: {
|
||||||
name: 'frequency_penalty',
|
name: 'frequency_penalty',
|
||||||
title: 'Frequency Penalty',
|
title: 'Frequency Penalty',
|
||||||
description: 'Frequency Penalty',
|
description:
|
||||||
|
'Adjusts the likelihood of the model repeating words or phrases in its output. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
min: 0,
|
||||||
@ -104,7 +106,8 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
presence_penalty: {
|
presence_penalty: {
|
||||||
name: 'presence_penalty',
|
name: 'presence_penalty',
|
||||||
title: 'Presence Penalty',
|
title: 'Presence Penalty',
|
||||||
description: 'Presence Penalty',
|
description:
|
||||||
|
'Influences the generation of new and varied concepts in the model’s output. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
min: 0,
|
||||||
@ -116,7 +119,7 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
top_p: {
|
top_p: {
|
||||||
name: 'top_p',
|
name: 'top_p',
|
||||||
title: 'Top P',
|
title: 'Top P',
|
||||||
description: 'Top P',
|
description: 'Set probability threshold for more relevant outputs.',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 0,
|
min: 0,
|
||||||
@ -128,10 +131,11 @@ export const presetConfiguration: Record<string, SettingComponentData> = {
|
|||||||
n_parallel: {
|
n_parallel: {
|
||||||
name: 'n_parallel',
|
name: 'n_parallel',
|
||||||
title: 'N Parallel',
|
title: 'N Parallel',
|
||||||
description: 'N Parallel',
|
description:
|
||||||
|
'The number of parallel operations. Only set when enable continuous batching. ',
|
||||||
controllerType: 'slider',
|
controllerType: 'slider',
|
||||||
controllerData: {
|
controllerData: {
|
||||||
min: 1,
|
min: 0,
|
||||||
max: 4,
|
max: 4,
|
||||||
step: 1,
|
step: 1,
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export type InputData = {
|
|||||||
export type SliderData = {
|
export type SliderData = {
|
||||||
min: number
|
min: number
|
||||||
max: number
|
max: number
|
||||||
|
|
||||||
step: number
|
step: number
|
||||||
value: number
|
value: number
|
||||||
}
|
}
|
||||||
@ -29,48 +30,58 @@ type CheckboxData = {
|
|||||||
checked: boolean
|
checked: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingComponentBuilder = (componentData: SettingComponentData[]) => {
|
const settingComponentBuilder = (
|
||||||
const components = componentData.map((data) => {
|
componentData: SettingComponentData[],
|
||||||
switch (data.controllerType) {
|
onlyPrompt?: boolean
|
||||||
case 'slider':
|
) => {
|
||||||
const { min, max, step, value } = data.controllerData as SliderData
|
const components = componentData
|
||||||
return (
|
.filter((x) =>
|
||||||
<Slider
|
onlyPrompt ? x.name === 'prompt_template' : x.name !== 'prompt_template'
|
||||||
key={data.name}
|
)
|
||||||
title={data.title}
|
.map((data) => {
|
||||||
min={min}
|
switch (data.controllerType) {
|
||||||
max={max}
|
case 'slider':
|
||||||
step={step}
|
const { min, max, step, value } = data.controllerData as SliderData
|
||||||
value={value}
|
return (
|
||||||
name={data.name}
|
<Slider
|
||||||
/>
|
key={data.name}
|
||||||
)
|
title={data.title}
|
||||||
case 'input':
|
description={data.description}
|
||||||
const { placeholder, value: textValue } =
|
min={min}
|
||||||
data.controllerData as InputData
|
max={max}
|
||||||
return (
|
step={step}
|
||||||
<ModelConfigInput
|
value={value}
|
||||||
title={data.title}
|
name={data.name}
|
||||||
key={data.name}
|
/>
|
||||||
name={data.name}
|
)
|
||||||
placeholder={placeholder}
|
case 'input':
|
||||||
value={textValue}
|
const { placeholder, value: textValue } =
|
||||||
/>
|
data.controllerData as InputData
|
||||||
)
|
return (
|
||||||
case 'checkbox':
|
<ModelConfigInput
|
||||||
const { checked } = data.controllerData as CheckboxData
|
title={data.title}
|
||||||
return (
|
key={data.name}
|
||||||
<Checkbox
|
name={data.name}
|
||||||
key={data.name}
|
description={data.description}
|
||||||
name={data.name}
|
placeholder={placeholder}
|
||||||
title={data.title}
|
value={textValue}
|
||||||
checked={checked}
|
/>
|
||||||
/>
|
)
|
||||||
)
|
case 'checkbox':
|
||||||
default:
|
const { checked } = data.controllerData as CheckboxData
|
||||||
return null
|
return (
|
||||||
}
|
<Checkbox
|
||||||
})
|
key={data.name}
|
||||||
|
name={data.name}
|
||||||
|
description={data.description}
|
||||||
|
title={data.title}
|
||||||
|
checked={checked}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return <div className="flex flex-col gap-y-4">{components}</div>
|
return <div className="flex flex-col gap-y-4">{components}</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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'
|
import { getUserSpace, openFileExplorer, joinPath } from '@janhq/core'
|
||||||
|
|
||||||
@ -10,19 +11,21 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
import CardSidebar from '@/containers/CardSidebar'
|
import CardSidebar from '@/containers/CardSidebar'
|
||||||
|
|
||||||
import DropdownListSidebar, {
|
import DropdownListSidebar, {
|
||||||
selectedModelAtom,
|
selectedModelAtom,
|
||||||
} from '@/containers/DropdownListSidebar'
|
} from '@/containers/DropdownListSidebar'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
|
|
||||||
|
import { getConfigurationsData } from '@/utils/componentSettings'
|
||||||
import { toSettingParams } from '@/utils/model_param'
|
import { toSettingParams } from '@/utils/model_param'
|
||||||
|
|
||||||
import EngineSetting from '../EngineSetting'
|
import EngineSetting from '../EngineSetting'
|
||||||
import ModelSetting from '../ModelSetting'
|
import ModelSetting from '../ModelSetting'
|
||||||
|
|
||||||
|
import settingComponentBuilder from '../ModelSetting/settingComponentBuilder'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
getActiveThreadModelParamsAtom,
|
getActiveThreadModelParamsAtom,
|
||||||
@ -34,13 +37,14 @@ export const showRightSideBarAtom = atom<boolean>(true)
|
|||||||
const Sidebar: React.FC = () => {
|
const Sidebar: React.FC = () => {
|
||||||
const showing = useAtomValue(showRightSideBarAtom)
|
const showing = useAtomValue(showRightSideBarAtom)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
const { updateThreadMetadata } = useCreateNewThread()
|
const { updateThreadMetadata } = useCreateNewThread()
|
||||||
const threadStates = useAtomValue(threadStatesAtom)
|
|
||||||
const { experimentalFeatureEnabed } = useContext(FeatureToggleContext)
|
|
||||||
|
|
||||||
const activeModelParams = useAtomValue(getActiveThreadModelParamsAtom)
|
const threadStates = useAtomValue(threadStatesAtom)
|
||||||
const modelSettingParams = toSettingParams(activeModelParams)
|
|
||||||
|
const modelEngineParams = toSettingParams(activeModelParams)
|
||||||
|
const componentDataEngineSetting = getConfigurationsData(modelEngineParams)
|
||||||
|
|
||||||
const onReviewInFinderClick = async (type: string) => {
|
const onReviewInFinderClick = async (type: string) => {
|
||||||
if (!activeThread) return
|
if (!activeThread) return
|
||||||
@ -119,48 +123,43 @@ const Sidebar: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={twMerge(
|
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'
|
showing ? 'animate-enter opacity-100' : 'opacity-0'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CardSidebar
|
<div className="flex flex-col space-y-4 p-4">
|
||||||
title="Thread"
|
<div>
|
||||||
onRevealInFinderClick={onReviewInFinderClick}
|
<label
|
||||||
onViewJsonClick={onViewJsonClick}
|
id="thread-title"
|
||||||
>
|
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
||||||
<div className="flex flex-col space-y-4 p-2">
|
>
|
||||||
<div>
|
Title
|
||||||
<label
|
</label>
|
||||||
id="thread-title"
|
<Input
|
||||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
id="thread-title"
|
||||||
>
|
value={activeThread?.title}
|
||||||
Title
|
onChange={(e) => {
|
||||||
</label>
|
if (activeThread)
|
||||||
<Input
|
updateThreadMetadata({
|
||||||
id="thread-title"
|
...activeThread,
|
||||||
value={activeThread?.title}
|
title: e.target.value || '',
|
||||||
onChange={(e) => {
|
})
|
||||||
if (activeThread)
|
}}
|
||||||
updateThreadMetadata({
|
/>
|
||||||
...activeThread,
|
|
||||||
title: e.target.value || '',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<label
|
|
||||||
id="thread-title"
|
|
||||||
className="mb-2 inline-block font-bold text-gray-600 dark:text-gray-300"
|
|
||||||
>
|
|
||||||
Threads ID
|
|
||||||
</label>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{activeThread?.id || '-'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
<div className="flex flex-col">
|
||||||
|
<label
|
||||||
|
id="thread-title"
|
||||||
|
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
Threads ID
|
||||||
|
</label>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{activeThread?.id || '-'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<CardSidebar
|
<CardSidebar
|
||||||
title="Assistant"
|
title="Assistant"
|
||||||
onRevealInFinderClick={onReviewInFinderClick}
|
onRevealInFinderClick={onReviewInFinderClick}
|
||||||
@ -176,7 +175,7 @@ const Sidebar: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
id="thread-title"
|
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
|
Instructions
|
||||||
</label>
|
</label>
|
||||||
@ -198,28 +197,60 @@ const Sidebar: React.FC = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Temporary disabled */}
|
||||||
|
{/* <div>
|
||||||
|
<label
|
||||||
|
id="tool-title"
|
||||||
|
className="mb-2 inline-block font-bold text-zinc-500 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
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>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
{experimentalFeatureEnabed && Object.keys(modelSettingParams).length ? (
|
|
||||||
<CardSidebar
|
|
||||||
title="Engine"
|
|
||||||
onRevealInFinderClick={onReviewInFinderClick}
|
|
||||||
onViewJsonClick={onViewJsonClick}
|
|
||||||
>
|
|
||||||
<div className="p-2">
|
|
||||||
<EngineSetting />
|
|
||||||
</div>
|
|
||||||
</CardSidebar>
|
|
||||||
) : null}
|
|
||||||
<CardSidebar
|
<CardSidebar
|
||||||
title="Model"
|
title="Model"
|
||||||
onRevealInFinderClick={onReviewInFinderClick}
|
onRevealInFinderClick={onReviewInFinderClick}
|
||||||
onViewJsonClick={onViewJsonClick}
|
onViewJsonClick={onViewJsonClick}
|
||||||
>
|
>
|
||||||
<div className="p-2">
|
<div className="px-2">
|
||||||
<DropdownListSidebar />
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<ModelSetting />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
|
|
||||||
import LogoMark from '@/containers/Brand/Logo/Mark'
|
import LogoMark from '@/containers/Brand/Logo/Mark'
|
||||||
|
|
||||||
|
import ModelReload from '@/containers/Loader/ModelReload'
|
||||||
import ModelStart from '@/containers/Loader/ModelStart'
|
import ModelStart from '@/containers/Loader/ModelStart'
|
||||||
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
||||||
|
|
||||||
@ -30,8 +31,10 @@ import ThreadList from '@/screens/Chat/ThreadList'
|
|||||||
import Sidebar, { showRightSideBarAtom } from './Sidebar'
|
import Sidebar, { showRightSideBarAtom } from './Sidebar'
|
||||||
|
|
||||||
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
import { getCurrentChatMessagesAtom } from '@/helpers/atoms/ChatMessage.atom'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
activeThreadAtom,
|
activeThreadAtom,
|
||||||
|
engineParamsUpdateAtom,
|
||||||
getActiveThreadIdAtom,
|
getActiveThreadIdAtom,
|
||||||
waitingToSendMessage,
|
waitingToSendMessage,
|
||||||
} from '@/helpers/atoms/Thread.atom'
|
} from '@/helpers/atoms/Thread.atom'
|
||||||
@ -48,9 +51,10 @@ const ChatScreen = () => {
|
|||||||
|
|
||||||
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
const [currentPrompt, setCurrentPrompt] = useAtom(currentPromptAtom)
|
||||||
const activeThreadState = useAtomValue(activeThreadStateAtom)
|
const activeThreadState = useAtomValue(activeThreadStateAtom)
|
||||||
const { sendChatMessage, queuedMessage } = useSendChatMessage()
|
const { sendChatMessage, queuedMessage, reloadModel } = useSendChatMessage()
|
||||||
const isWaitingForResponse = activeThreadState?.waitingForResponse ?? false
|
const isWaitingForResponse = activeThreadState?.waitingForResponse ?? false
|
||||||
const disabled = currentPrompt.trim().length === 0 || isWaitingForResponse
|
const isDisabledChatbox =
|
||||||
|
currentPrompt.trim().length === 0 || isWaitingForResponse
|
||||||
|
|
||||||
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
const activeThreadId = useAtomValue(getActiveThreadIdAtom)
|
||||||
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
const [isWaitingToSend, setIsWaitingToSend] = useAtom(waitingToSendMessage)
|
||||||
@ -59,6 +63,7 @@ const ChatScreen = () => {
|
|||||||
|
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||||
const modelRef = useRef(activeModel)
|
const modelRef = useRef(activeModel)
|
||||||
|
const engineParamsUpdate = useAtomValue(engineParamsUpdateAtom)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modelRef.current = activeModel
|
modelRef.current = activeModel
|
||||||
@ -144,18 +149,30 @@ const ChatScreen = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ModelStart />
|
{!engineParamsUpdate && <ModelStart />}
|
||||||
|
|
||||||
{queuedMessage && (
|
{reloadModel && (
|
||||||
<div className="my-2 py-2 text-center">
|
<>
|
||||||
<span className="rounded-lg border border-border px-4 py-2 shadow-lg">
|
<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
|
Message queued. It can be sent once the model has started
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mx-auto flex w-full flex-shrink-0 items-end justify-center space-x-4 px-8 py-4">
|
<div className="mx-auto flex w-full flex-shrink-0 items-end justify-center space-x-4 px-8 py-4">
|
||||||
<Textarea
|
<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' }}
|
style={{ height: '40px' }}
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
onKeyDown={(e: KeyboardEvent<HTMLTextAreaElement>) =>
|
onKeyDown={(e: KeyboardEvent<HTMLTextAreaElement>) =>
|
||||||
@ -171,7 +188,9 @@ const ChatScreen = () => {
|
|||||||
{messages[messages.length - 1]?.status !== MessageStatus.Pending ? (
|
{messages[messages.length - 1]?.status !== MessageStatus.Pending ? (
|
||||||
<Button
|
<Button
|
||||||
size="lg"
|
size="lg"
|
||||||
disabled={disabled || stateModel.loading || !activeThread}
|
disabled={
|
||||||
|
isDisabledChatbox || stateModel.loading || !activeThread
|
||||||
|
}
|
||||||
themes="primary"
|
themes="primary"
|
||||||
className="min-w-[100px]"
|
className="min-w-[100px]"
|
||||||
onClick={sendChatMessage}
|
onClick={sendChatMessage}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { getAssistants } from '@/hooks/useGetAssistants'
|
|||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
import { toGibibytes } from '@/utils/converter'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: Model
|
model: Model
|
||||||
@ -99,7 +99,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({ model, onClick, open }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="inline-flex items-center space-x-2">
|
<div className="inline-flex items-center space-x-2">
|
||||||
<span className="mr-4 font-semibold text-muted-foreground">
|
<span className="mr-4 font-semibold text-muted-foreground">
|
||||||
{toGigabytes(model.metadata.size)}
|
{toGibibytes(model.metadata.size)}
|
||||||
</span>
|
</span>
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
|
|||||||
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