commit
9e7bdc7f2a
31
.github/workflows/clean-cloudflare-r2.yml
vendored
31
.github/workflows/clean-cloudflare-r2.yml
vendored
@ -1,31 +0,0 @@
|
|||||||
name: "Clean Cloudflare R2 nightly build artifacts older than 10 days"
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # every day at 00:00
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
clean-cloudflare-r2:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: production
|
|
||||||
steps:
|
|
||||||
- name: install-aws-cli-action
|
|
||||||
uses: unfor19/install-aws-cli-action@v1
|
|
||||||
- name: Delete object older than 10 days
|
|
||||||
run: |
|
|
||||||
# Get the list of objects in the 'latest' folder
|
|
||||||
OBJECTS=$(aws s3api list-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --prefix "latest/" --query 'Contents[?LastModified<`'$(date -d "$current_date -10 days" -u +"%Y-%m-%dT%H:%M:%SZ")'`].{Key: Key}' --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | jq -c .)
|
|
||||||
|
|
||||||
# Create a JSON file for the delete operation
|
|
||||||
echo "{\"Objects\": $OBJECTS, \"Quiet\": false}" > delete.json
|
|
||||||
|
|
||||||
# Delete the objects
|
|
||||||
echo q | aws s3api delete-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --delete file://delete.json --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
||||||
|
|
||||||
# Remove the JSON file
|
|
||||||
rm delete.json
|
|
||||||
env:
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_DEFAULT_REGION: auto
|
|
||||||
AWS_EC2_METADATA_DISABLED: "true"
|
|
||||||
205
.github/workflows/jan-electron-linter-and-test.yml
vendored
205
.github/workflows/jan-electron-linter-and-test.yml
vendored
@ -38,17 +38,57 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-on-macos:
|
test-on-macos:
|
||||||
|
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
runs-on: [self-hosted, macOS, macos-desktop]
|
runs-on: [self-hosted, macOS, macos-desktop]
|
||||||
steps:
|
steps:
|
||||||
- name: "Cleanup build folder"
|
|
||||||
run: |
|
|
||||||
ls -la ./
|
|
||||||
rm -rf ./* || true
|
|
||||||
rm -rf ./.??* || true
|
|
||||||
ls -la ./
|
|
||||||
rm -rf ~/Library/Application\ Support/jan
|
|
||||||
- name: Getting the repo
|
- name: Getting the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: "Cleanup cache"
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
|
||||||
|
- name: Get Commit Message for PR
|
||||||
|
if : github.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Get Commit Message for push event
|
||||||
|
if : github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: "Config report portal"
|
||||||
|
run: |
|
||||||
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
run: |
|
||||||
|
npm config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
make test
|
||||||
|
env:
|
||||||
|
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||||
|
TURBO_API: "${{ secrets.TURBO_API }}"
|
||||||
|
TURBO_TEAM: "macos"
|
||||||
|
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
||||||
|
|
||||||
|
test-on-macos-pr-target:
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
runs-on: [self-hosted, macOS, macos-desktop]
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
@ -62,29 +102,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Linter and test
|
- name: Linter and test
|
||||||
run: |
|
run: |
|
||||||
|
npm config set registry https://registry.npmjs.org --global
|
||||||
|
yarn config set registry https://registry.npmjs.org --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||||
|
|
||||||
test-on-windows:
|
test-on-windows:
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
||||||
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
||||||
steps:
|
steps:
|
||||||
- name: Clean workspace
|
|
||||||
run: |
|
|
||||||
Remove-Item -Path "\\?\$(Get-Location)\*" -Force -Recurse
|
|
||||||
$path = "$Env:APPDATA\jan"
|
|
||||||
if (Test-Path $path) {
|
|
||||||
Remove-Item "\\?\$path" -Recurse -Force
|
|
||||||
} else {
|
|
||||||
Write-Output "Folder does not exist."
|
|
||||||
}
|
|
||||||
- name: Getting the repo
|
- name: Getting the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
@ -97,26 +132,79 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
|
|
||||||
|
- name: Get Commit Message for push event
|
||||||
|
if : github.event_name == 'push'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: "Config report portal"
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
|
|
||||||
- name: Linter and test
|
- name: Linter and test
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
|
npm config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
make test
|
make test
|
||||||
|
env:
|
||||||
|
TURBO_API: "${{ secrets.TURBO_API }}"
|
||||||
|
TURBO_TEAM: "windows"
|
||||||
|
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
||||||
test-on-windows-pr:
|
test-on-windows-pr:
|
||||||
if: github.event_name == 'pull_request'
|
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
runs-on: windows-desktop-default-windows-security
|
runs-on: windows-desktop-default-windows-security
|
||||||
steps:
|
steps:
|
||||||
- name: Clean workspace
|
|
||||||
run: |
|
|
||||||
Remove-Item -Path "\\?\$(Get-Location)\*" -Force -Recurse
|
|
||||||
$path = "$Env:APPDATA\jan"
|
|
||||||
if (Test-Path $path) {
|
|
||||||
Remove-Item "\\?\$path" -Recurse -Force
|
|
||||||
} else {
|
|
||||||
Write-Output "Folder does not exist."
|
|
||||||
}
|
|
||||||
- name: Getting the repo
|
- name: Getting the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
# Clean cache, continue on error
|
||||||
|
- name: "Cleanup cache"
|
||||||
|
shell: powershell
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
|
||||||
|
- name: Get Commit Message for PR
|
||||||
|
if : github.event_name == 'pull_request'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: "Config report portal"
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
npm config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
make test
|
||||||
|
env:
|
||||||
|
TURBO_API: "${{ secrets.TURBO_API }}"
|
||||||
|
TURBO_TEAM: "windows"
|
||||||
|
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
||||||
|
|
||||||
|
test-on-windows-pr-target:
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
runs-on: windows-desktop-default-windows-security
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@ -133,20 +221,65 @@ jobs:
|
|||||||
- name: Linter and test
|
- name: Linter and test
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
|
npm config set registry https://registry.npmjs.org --global
|
||||||
|
yarn config set registry https://registry.npmjs.org --global
|
||||||
make test
|
make test
|
||||||
|
|
||||||
|
|
||||||
test-on-ubuntu:
|
test-on-ubuntu:
|
||||||
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
||||||
|
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
steps:
|
steps:
|
||||||
- name: "Cleanup build folder"
|
|
||||||
run: |
|
|
||||||
ls -la ./
|
|
||||||
rm -rf ./* || true
|
|
||||||
rm -rf ./.??* || true
|
|
||||||
ls -la ./
|
|
||||||
rm -rf ~/.config/jan
|
|
||||||
- name: Getting the repo
|
- name: Getting the repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Installing node
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
|
||||||
|
- name: "Cleanup cache"
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
|
||||||
|
- name: Get Commit Message for PR
|
||||||
|
if : github.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Get Commit Message for push event
|
||||||
|
if : github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: "Config report portal"
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
|
|
||||||
|
- name: Linter and test
|
||||||
|
run: |
|
||||||
|
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||||
|
echo -e "Display ID: $DISPLAY"
|
||||||
|
npm config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
|
make test
|
||||||
|
env:
|
||||||
|
TURBO_API: "${{ secrets.TURBO_API }}"
|
||||||
|
TURBO_TEAM: "linux"
|
||||||
|
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
||||||
|
|
||||||
|
test-on-ubuntu-pr-target:
|
||||||
|
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
||||||
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
|
steps:
|
||||||
|
- name: Getting the repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
@ -162,4 +295,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||||
echo -e "Display ID: $DISPLAY"
|
echo -e "Display ID: $DISPLAY"
|
||||||
make test
|
npm config set registry https://registry.npmjs.org --global
|
||||||
|
yarn config set registry https://registry.npmjs.org --global
|
||||||
|
make test
|
||||||
@ -47,27 +47,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
args: |
|
args: |
|
||||||
Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}:
|
Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}:
|
||||||
- Windows: https://delta.jan.ai/latest/jan-win-x64-{{ VERSION }}.exe
|
- Windows: https://app.jan.ai/download/nightly/win-x64
|
||||||
- macOS Intel: https://delta.jan.ai/latest/jan-mac-x64-{{ VERSION }}.dmg
|
- macOS Intel: https://app.jan.ai/download/nightly/mac-x64
|
||||||
- macOS Apple Silicon: https://delta.jan.ai/latest/jan-mac-arm64-{{ VERSION }}.dmg
|
- macOS Apple Silicon: https://app.jan.ai/download/nightly/mac-arm64
|
||||||
- Linux Deb: https://delta.jan.ai/latest/jan-linux-amd64-{{ VERSION }}.deb
|
- Linux Deb: https://app.jan.ai/download/nightly/linux-amd64-deb
|
||||||
- Linux AppImage: https://delta.jan.ai/latest/jan-linux-x86_64-{{ VERSION }}.AppImage
|
- Linux AppImage: https://app.jan.ai/download/nightly/linux-amd64-appimage
|
||||||
- Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
- 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
|
|
||||||
run: |
|
|
||||||
sed -i "s|<a href='https://delta.jan.ai/latest/jan-win-x64-.*'>|<a href='https://delta.jan.ai/latest/jan-win-x64-${{ inputs.new_version }}.exe'>|" README.md
|
|
||||||
sed -i "s|<a href='https://delta.jan.ai/latest/jan-mac-x64-.*'>|<a href='https://delta.jan.ai/latest/jan-mac-x64-${{ inputs.new_version }}.dmg'>|" README.md
|
|
||||||
sed -i "s|<a href='https://delta.jan.ai/latest/jan-mac-arm64-.*'>|<a href='https://delta.jan.ai/latest/jan-mac-arm64-${{ inputs.new_version }}.dmg'>|" README.md
|
|
||||||
sed -i "s|<a href='https://delta.jan.ai/latest/jan-linux-amd64-.*'>|<a href='https://delta.jan.ai/latest/jan-linux-amd64-${{ inputs.new_version }}.deb'>|" README.md
|
|
||||||
sed -i "s|<a href='https://delta.jan.ai/latest/jan-linux-x86_64-.*'>|<a href='https://delta.jan.ai/latest/jan-linux-x86_64-${{ inputs.new_version }}.AppImage'>|" README.md
|
|
||||||
cat README.md
|
|
||||||
git config --global user.email "service@jan.ai"
|
|
||||||
git config --global user.name "Service Account"
|
|
||||||
git add README.md
|
|
||||||
git commit -m "${GITHUB_REPOSITORY}: Update README.md with nightly build artifact URL"
|
|
||||||
git -c http.extraheader="AUTHORIZATION: bearer ${{ secrets.PAT_SERVICE_ACCOUNT }}" push origin HEAD:${{ inputs.push_to_branch }}
|
|
||||||
env:
|
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
|
||||||
49
.github/workflows/update-release-url.yml
vendored
49
.github/workflows/update-release-url.yml
vendored
@ -1,49 +0,0 @@
|
|||||||
name: Update Download URLs
|
|
||||||
|
|
||||||
on:
|
|
||||||
release:
|
|
||||||
types:
|
|
||||||
- published
|
|
||||||
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update-readme:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
environment: production
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: "0"
|
|
||||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
|
||||||
ref: dev
|
|
||||||
|
|
||||||
- name: Get Latest Release
|
|
||||||
uses: pozetroninc/github-action-get-latest-release@v0.7.0
|
|
||||||
id: get-latest-release
|
|
||||||
with:
|
|
||||||
repository: ${{ github.repository }}
|
|
||||||
|
|
||||||
- name: Update Download URLs in README.md
|
|
||||||
run: |
|
|
||||||
echo "Latest Release: ${{ steps.get-latest-release.outputs.release }}"
|
|
||||||
tag=$(/bin/echo -n "${{ steps.get-latest-release.outputs.release }}")
|
|
||||||
echo "Tag: $tag"
|
|
||||||
# Remove the v prefix
|
|
||||||
release=${tag:1}
|
|
||||||
echo "Release: $release"
|
|
||||||
sed -i "s|<a href='https://github.com/janhq/jan/releases/download/v.*/jan-win-x64-.*'>|<a href='https://github.com/janhq/jan/releases/download/v${release}/jan-win-x64-${release}.exe'>|" README.md
|
|
||||||
sed -i "s|<a href='https://github.com/janhq/jan/releases/download/v.*/jan-mac-x64-.*'>|<a href='https://github.com/janhq/jan/releases/download/v${release}/jan-mac-x64-${release}.dmg'>|" README.md
|
|
||||||
sed -i "s|<a href='https://github.com/janhq/jan/releases/download/v.*/jan-mac-arm64-.*'>|<a href='https://github.com/janhq/jan/releases/download/v${release}/jan-mac-arm64-${release}.dmg'>|" README.md
|
|
||||||
sed -i "s|<a href='https://github.com/janhq/jan/releases/download/v.*/jan-linux-amd64-.*'>|<a href='https://github.com/janhq/jan/releases/download/v${release}/jan-linux-amd64-${release}.deb'>|" README.md
|
|
||||||
sed -i "s|<a href='https://github.com/janhq/jan/releases/download/v.*/jan-linux-x86_64-.*'>|<a href='https://github.com/janhq/jan/releases/download/v${release}/jan-linux-x86_64-${release}.AppImage'>|" README.md
|
|
||||||
|
|
||||||
- name: Commit and Push changes
|
|
||||||
if: github.event_name == 'release'
|
|
||||||
run: |
|
|
||||||
git config --global user.email "service@jan.ai"
|
|
||||||
git config --global user.name "Service Account"
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Update README.md with Stable Download URLs"
|
|
||||||
git -c http.extraheader="AUTHORIZATION: bearer ${{ secrets.PAT_SERVICE_ACCOUNT }}" push origin HEAD:dev
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,5 +1,7 @@
|
|||||||
.vscode
|
.vscode
|
||||||
|
.idea
|
||||||
.env
|
.env
|
||||||
|
.idea
|
||||||
|
|
||||||
# Jan inference
|
# Jan inference
|
||||||
error.log
|
error.log
|
||||||
@ -35,4 +37,4 @@ extensions/*-extension/bin/vulkaninfo
|
|||||||
|
|
||||||
|
|
||||||
# Turborepo
|
# Turborepo
|
||||||
.turbo
|
.turbo
|
||||||
|
|||||||
@ -41,7 +41,6 @@ COPY --from=builder /app/pre-install ./pre-install/
|
|||||||
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/uikit ./uikit/
|
COPY --from=builder /app/uikit ./uikit/
|
||||||
COPY --from=builder /app/web ./web/
|
COPY --from=builder /app/web ./web/
|
||||||
COPY --from=builder /app/models ./models/
|
|
||||||
|
|
||||||
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
||||||
RUN yarn workspace @janhq/web install
|
RUN yarn workspace @janhq/web install
|
||||||
|
|||||||
@ -65,7 +65,6 @@ COPY --from=builder /app/pre-install ./pre-install/
|
|||||||
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/uikit ./uikit/
|
COPY --from=builder /app/uikit ./uikit/
|
||||||
COPY --from=builder /app/web ./web/
|
COPY --from=builder /app/web ./web/
|
||||||
COPY --from=builder /app/models ./models/
|
|
||||||
|
|
||||||
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
||||||
RUN yarn workspace @janhq/web install
|
RUN yarn workspace @janhq/web install
|
||||||
|
|||||||
82
Makefile
82
Makefile
@ -1,5 +1,11 @@
|
|||||||
# Makefile for Jan Electron App - Build, Lint, Test, and Clean
|
# Makefile for Jan Electron App - Build, Lint, Test, and Clean
|
||||||
|
|
||||||
|
REPORT_PORTAL_URL ?= ""
|
||||||
|
REPORT_PORTAL_API_KEY ?= ""
|
||||||
|
REPORT_PORTAL_PROJECT_NAME ?= ""
|
||||||
|
REPORT_PORTAL_LAUNCH_NAME ?= "Jan App"
|
||||||
|
REPORT_PORTAL_DESCRIPTION ?= "Jan App report"
|
||||||
|
|
||||||
# Default target, does nothing
|
# Default target, does nothing
|
||||||
all:
|
all:
|
||||||
@echo "Specify a target to run"
|
@echo "Specify a target to run"
|
||||||
@ -37,6 +43,64 @@ dev: check-file-counts
|
|||||||
lint: check-file-counts
|
lint: check-file-counts
|
||||||
yarn lint
|
yarn lint
|
||||||
|
|
||||||
|
update-playwright-config:
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
echo -e "const RPconfig = {\n\
|
||||||
|
apiKey: '$(REPORT_PORTAL_API_KEY)',\n\
|
||||||
|
endpoint: '$(REPORT_PORTAL_URL)',\n\
|
||||||
|
project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\
|
||||||
|
launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\
|
||||||
|
attributes: [\n\
|
||||||
|
{\n\
|
||||||
|
key: 'key',\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
{\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
],\n\
|
||||||
|
description: '$(REPORT_PORTAL_DESCRIPTION)',\n\
|
||||||
|
}\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts;
|
||||||
|
sed -i "s/^ reporter: .*/ reporter: [['@reportportal\/agent-js-playwright', RPconfig]],/" electron/playwright.config.ts
|
||||||
|
|
||||||
|
else ifeq ($(shell uname -s),Linux)
|
||||||
|
echo "const RPconfig = {\n\
|
||||||
|
apiKey: '$(REPORT_PORTAL_API_KEY)',\n\
|
||||||
|
endpoint: '$(REPORT_PORTAL_URL)',\n\
|
||||||
|
project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\
|
||||||
|
launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\
|
||||||
|
attributes: [\n\
|
||||||
|
{\n\
|
||||||
|
key: 'key',\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
{\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
],\n\
|
||||||
|
description: '$(REPORT_PORTAL_DESCRIPTION)',\n\
|
||||||
|
}\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts;
|
||||||
|
sed -i "s/^ reporter: .*/ reporter: [['@reportportal\/agent-js-playwright', RPconfig]],/" electron/playwright.config.ts
|
||||||
|
else
|
||||||
|
echo "const RPconfig = {\n\
|
||||||
|
apiKey: '$(REPORT_PORTAL_API_KEY)',\n\
|
||||||
|
endpoint: '$(REPORT_PORTAL_URL)',\n\
|
||||||
|
project: '$(REPORT_PORTAL_PROJECT_NAME)',\n\
|
||||||
|
launch: '$(REPORT_PORTAL_LAUNCH_NAME)',\n\
|
||||||
|
attributes: [\n\
|
||||||
|
{\n\
|
||||||
|
key: 'key',\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
{\n\
|
||||||
|
value: 'value',\n\
|
||||||
|
},\n\
|
||||||
|
],\n\
|
||||||
|
description: '$(REPORT_PORTAL_DESCRIPTION)',\n\
|
||||||
|
}\n$$(cat electron/playwright.config.ts)" > electron/playwright.config.ts;
|
||||||
|
sed -i '' "s|^ reporter: .*| reporter: [['@reportportal\/agent-js-playwright', RPconfig]],|" electron/playwright.config.ts
|
||||||
|
endif
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
test: lint
|
test: lint
|
||||||
yarn build:test
|
yarn build:test
|
||||||
@ -53,19 +117,24 @@ build: check-file-counts
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist, build, out -Recurse -Directory | Remove-Item -Recurse -Force"
|
-powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist, build, out, .turbo -Recurse -Directory | Remove-Item -Recurse -Force"
|
||||||
powershell -Command "Get-ChildItem -Path . -Include package-lock.json -Recurse -File | Remove-Item -Recurse -Force"
|
-powershell -Command "Get-ChildItem -Path . -Include package-lock.json -Recurse -File | Remove-Item -Recurse -Force"
|
||||||
powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz"
|
-powershell -Command "Get-ChildItem -Path . -Include yarn.lock -Recurse -File | Remove-Item -Recurse -Force"
|
||||||
powershell -Command "Remove-Item -Recurse -Force ./electron/pre-install/*.tgz"
|
-powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz"
|
||||||
powershell -Command "if (Test-Path \"$($env:USERPROFILE)\jan\extensions\") { Remove-Item -Path \"$($env:USERPROFILE)\jan\extensions\" -Recurse -Force }"
|
-powershell -Command "Remove-Item -Recurse -Force ./extensions/*/*.tgz"
|
||||||
|
-powershell -Command "Remove-Item -Recurse -Force ./electron/pre-install/*.tgz"
|
||||||
|
-powershell -Command "if (Test-Path \"$($env:USERPROFILE)\jan\extensions\") { Remove-Item -Path \"$($env:USERPROFILE)\jan\extensions\" -Recurse -Force }"
|
||||||
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 '{}' +
|
||||||
find . -name "build" -type d -exec rm -rf '{}' +
|
find . -name "build" -type d -exec rm -rf '{}' +
|
||||||
find . -name "out" -type d -exec rm -rf '{}' +
|
find . -name "out" -type d -exec rm -rf '{}' +
|
||||||
|
find . -name ".turbo" -type d -exec rm -rf '{}' +
|
||||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||||
|
find . -name "yarn.lock" -type f -exec rm -rf '{}' +
|
||||||
rm -rf ./pre-install/*.tgz
|
rm -rf ./pre-install/*.tgz
|
||||||
|
rm -rf ./extensions/*/*.tgz
|
||||||
rm -rf ./electron/pre-install/*.tgz
|
rm -rf ./electron/pre-install/*.tgz
|
||||||
rm -rf "~/jan/extensions"
|
rm -rf "~/jan/extensions"
|
||||||
rm -rf "~/.cache/jan*"
|
rm -rf "~/.cache/jan*"
|
||||||
@ -75,8 +144,11 @@ else
|
|||||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||||
find . -name "build" -type d -exec rm -rf '{}' +
|
find . -name "build" -type d -exec rm -rf '{}' +
|
||||||
find . -name "out" -type d -exec rm -rf '{}' +
|
find . -name "out" -type d -exec rm -rf '{}' +
|
||||||
|
find . -name ".turbo" -type d -exec rm -rf '{}' +
|
||||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||||
|
find . -name "yarn.lock" -type f -exec rm -rf '{}' +
|
||||||
rm -rf ./pre-install/*.tgz
|
rm -rf ./pre-install/*.tgz
|
||||||
|
rm -rf ./extensions/*/*.tgz
|
||||||
rm -rf ./electron/pre-install/*.tgz
|
rm -rf ./electron/pre-install/*.tgz
|
||||||
rm -rf ~/jan/extensions
|
rm -rf ~/jan/extensions
|
||||||
rm -rf ~/Library/Caches/jan*
|
rm -rf ~/Library/Caches/jan*
|
||||||
|
|||||||
50
README.md
50
README.md
@ -43,32 +43,32 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<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.9/jan-win-x64-0.4.9.exe'>
|
<a href='https://app.jan.ai/download/latest/win-x64'>
|
||||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-x64-0.4.9.dmg'>
|
<a href='https://app.jan.ai/download/latest/mac-x64'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-arm64-0.4.9.dmg'>
|
<a href='https://app.jan.ai/download/latest/mac-arm64'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-linux-amd64-0.4.9.deb'>
|
<a href='https://app.jan.ai/download/latest/linux-amd64-deb'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-linux-x86_64-0.4.9.AppImage'>
|
<a href='https://app.jan.ai/download/latest/linux-amd64-appimage'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -76,32 +76,32 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.9-345.exe'>
|
<a href='https://app.jan.ai/download/nightly/win-x64'>
|
||||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.9-345.dmg'>
|
<a href='https://app.jan.ai/download/nightly/mac-x64'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.9-345.dmg'>
|
<a href='https://app.jan.ai/download/nightly/mac-arm64'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.9-345.deb'>
|
<a href='https://app.jan.ai/download/nightly/linux-amd64-deb'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.9-345.AppImage'>
|
<a href='https://app.jan.ai/download/nightly/linux-amd64-appimage'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -240,6 +240,7 @@ This will build the app MacOS m1/m2 for production (with code signing already do
|
|||||||
- If you intend to run Jan in GPU mode, you need to install `nvidia-driver` and `nvidia-docker2`. Follow the instruction [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) for installation.
|
- If you intend to run Jan in GPU mode, you need to install `nvidia-driver` and `nvidia-docker2`. Follow the instruction [here](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) for installation.
|
||||||
|
|
||||||
- Run Jan in Docker mode
|
- Run Jan in Docker mode
|
||||||
|
> User can choose between `docker-compose.yml` with latest prebuilt docker image or `docker-compose-dev.yml` with local docker build
|
||||||
|
|
||||||
| Docker compose Profile | Description |
|
| Docker compose Profile | Description |
|
||||||
| ---------------------- | -------------------------------------------- |
|
| ---------------------- | -------------------------------------------- |
|
||||||
@ -336,6 +337,15 @@ Jan builds on top of other open-source projects:
|
|||||||
- For business inquiries: email hello@jan.ai
|
- For business inquiries: email hello@jan.ai
|
||||||
- For jobs: please email hr@jan.ai
|
- For jobs: please email hr@jan.ai
|
||||||
|
|
||||||
|
## Trust & Safety
|
||||||
|
|
||||||
|
Beware of scams.
|
||||||
|
|
||||||
|
- We will never ask you for personal info
|
||||||
|
- We are a free product; there's no paid version
|
||||||
|
- We don't have a token or ICO
|
||||||
|
- We are not actively fundraising or seeking donations
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Jan is free and open source, under the AGPLv3 license.
|
Jan is free and open source, under the AGPLv3 license.
|
||||||
|
|||||||
@ -30,6 +30,7 @@ export default [
|
|||||||
// which external modules to include in the bundle
|
// which external modules to include in the bundle
|
||||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||||
replace({
|
replace({
|
||||||
|
'preventAssignment': true,
|
||||||
'node:crypto': 'crypto',
|
'node:crypto': 'crypto',
|
||||||
'delimiters': ['"', '"'],
|
'delimiters': ['"', '"'],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import { SettingComponentProps } from '../types'
|
||||||
|
import { getJanDataFolderPath, joinPath } from './core'
|
||||||
|
import { fs } from './fs'
|
||||||
|
|
||||||
export enum ExtensionTypeEnum {
|
export enum ExtensionTypeEnum {
|
||||||
Assistant = 'assistant',
|
Assistant = 'assistant',
|
||||||
Conversational = 'conversational',
|
Conversational = 'conversational',
|
||||||
@ -19,9 +23,9 @@ export interface Compatibility {
|
|||||||
const ALL_INSTALLATION_STATE = [
|
const ALL_INSTALLATION_STATE = [
|
||||||
'NotRequired', // not required.
|
'NotRequired', // not required.
|
||||||
'Installed', // require and installed. Good to go.
|
'Installed', // require and installed. Good to go.
|
||||||
'Updatable', // require and installed but need to be updated.
|
|
||||||
'NotInstalled', // require to be installed.
|
'NotInstalled', // require to be installed.
|
||||||
'Corrupted', // require but corrupted. Need to redownload.
|
'Corrupted', // require but corrupted. Need to redownload.
|
||||||
|
'NotCompatible', // require but not compatible.
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
export type InstallationStateTuple = typeof ALL_INSTALLATION_STATE
|
export type InstallationStateTuple = typeof ALL_INSTALLATION_STATE
|
||||||
@ -32,6 +36,43 @@ export type InstallationState = InstallationStateTuple[number]
|
|||||||
* This class should be extended by any class that represents an extension.
|
* This class should be extended by any class that represents an extension.
|
||||||
*/
|
*/
|
||||||
export abstract class BaseExtension implements ExtensionType {
|
export abstract class BaseExtension implements ExtensionType {
|
||||||
|
protected settingFolderName = 'settings'
|
||||||
|
protected settingFileName = 'settings.json'
|
||||||
|
|
||||||
|
/** @type {string} Name of the extension. */
|
||||||
|
name: string
|
||||||
|
|
||||||
|
/** @type {string} Product Name of the extension. */
|
||||||
|
productName?: string
|
||||||
|
|
||||||
|
/** @type {string} The URL of the extension to load. */
|
||||||
|
url: string
|
||||||
|
|
||||||
|
/** @type {boolean} Whether the extension is activated or not. */
|
||||||
|
active
|
||||||
|
|
||||||
|
/** @type {string} Extension's description. */
|
||||||
|
description
|
||||||
|
|
||||||
|
/** @type {string} Extension's version. */
|
||||||
|
version
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
url: string,
|
||||||
|
name: string,
|
||||||
|
productName?: string,
|
||||||
|
active?: boolean,
|
||||||
|
description?: string,
|
||||||
|
version?: string
|
||||||
|
) {
|
||||||
|
this.name = name
|
||||||
|
this.productName = productName
|
||||||
|
this.url = url
|
||||||
|
this.active = active
|
||||||
|
this.description = description
|
||||||
|
this.version = version
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the type of the extension.
|
* Returns the type of the extension.
|
||||||
* @returns {ExtensionType} The type of the extension
|
* @returns {ExtensionType} The type of the extension
|
||||||
@ -40,11 +81,13 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
type(): ExtensionTypeEnum | undefined {
|
type(): ExtensionTypeEnum | undefined {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is loaded.
|
* Called when the extension is loaded.
|
||||||
* Any initialization logic for the extension should be put here.
|
* Any initialization logic for the extension should be put here.
|
||||||
*/
|
*/
|
||||||
abstract onLoad(): void
|
abstract onLoad(): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the extension is unloaded.
|
* Called when the extension is unloaded.
|
||||||
* Any cleanup logic for the extension should be put here.
|
* Any cleanup logic for the extension should be put here.
|
||||||
@ -60,11 +103,40 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async registerSettings(settings: SettingComponentProps[]): Promise<void> {
|
||||||
* Determine if the extension is updatable.
|
if (!this.name) {
|
||||||
*/
|
console.error('Extension name is not defined')
|
||||||
updatable(): boolean {
|
return
|
||||||
return false
|
}
|
||||||
|
|
||||||
|
const extensionSettingFolderPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
this.name,
|
||||||
|
])
|
||||||
|
settings.forEach((setting) => {
|
||||||
|
setting.extensionName = this.name
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
await fs.mkdir(extensionSettingFolderPath)
|
||||||
|
const settingFilePath = await joinPath([extensionSettingFolderPath, this.settingFileName])
|
||||||
|
|
||||||
|
if (await fs.existsSync(settingFilePath)) return
|
||||||
|
await fs.writeFileSync(settingFilePath, JSON.stringify(settings, null, 2))
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSetting<T>(key: string, defaultValue: T) {
|
||||||
|
const keySetting = (await this.getSettings()).find((setting) => setting.key === key)
|
||||||
|
|
||||||
|
const value = keySetting?.controllerProps.value
|
||||||
|
return (value as T) ?? defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingUpdate<T>(key: string, value: T) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,8 +153,59 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
// @ts-ignore
|
async install(): Promise<void> {
|
||||||
async install(...args): Promise<void> {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getSettings(): Promise<SettingComponentProps[]> {
|
||||||
|
if (!this.name) return []
|
||||||
|
|
||||||
|
const settingPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
this.settingFolderName,
|
||||||
|
this.name,
|
||||||
|
this.settingFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = await fs.readFileSync(settingPath, 'utf-8')
|
||||||
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
|
return settings
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSettings(componentProps: Partial<SettingComponentProps>[]): Promise<void> {
|
||||||
|
if (!this.name) return
|
||||||
|
|
||||||
|
const settings = await this.getSettings()
|
||||||
|
|
||||||
|
const updatedSettings = settings.map((setting) => {
|
||||||
|
const updatedSetting = componentProps.find(
|
||||||
|
(componentProp) => componentProp.key === setting.key
|
||||||
|
)
|
||||||
|
if (updatedSetting && updatedSetting.controllerProps) {
|
||||||
|
setting.controllerProps.value = updatedSetting.controllerProps.value
|
||||||
|
}
|
||||||
|
return setting
|
||||||
|
})
|
||||||
|
|
||||||
|
const settingPath = await joinPath([
|
||||||
|
await getJanDataFolderPath(),
|
||||||
|
this.settingFolderName,
|
||||||
|
this.name,
|
||||||
|
this.settingFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
await fs.writeFileSync(settingPath, JSON.stringify(updatedSettings, null, 2))
|
||||||
|
|
||||||
|
updatedSettings.forEach((setting) => {
|
||||||
|
this.onSettingUpdate<typeof setting.controllerProps.value>(
|
||||||
|
setting.key,
|
||||||
|
setting.controllerProps.value
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import { EngineManager } from './EngineManager'
|
|||||||
* Applicable to all AI Engines
|
* Applicable to all AI Engines
|
||||||
*/
|
*/
|
||||||
export abstract class AIEngine extends BaseExtension {
|
export abstract class AIEngine extends BaseExtension {
|
||||||
|
private static modelsFolder = 'models'
|
||||||
|
|
||||||
// The inference engine
|
// The inference engine
|
||||||
abstract provider: string
|
abstract provider: string
|
||||||
|
|
||||||
@ -21,15 +23,6 @@ export abstract class AIEngine extends BaseExtension {
|
|||||||
|
|
||||||
events.on(ModelEvent.OnModelInit, (model: Model) => this.loadModel(model))
|
events.on(ModelEvent.OnModelInit, (model: Model) => this.loadModel(model))
|
||||||
events.on(ModelEvent.OnModelStop, (model: Model) => this.unloadModel(model))
|
events.on(ModelEvent.OnModelStop, (model: Model) => this.unloadModel(model))
|
||||||
|
|
||||||
this.prePopulateModels()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines models
|
|
||||||
*/
|
|
||||||
models(): Promise<Model[]> {
|
|
||||||
return Promise.resolve([])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,6 +32,49 @@ export abstract class AIEngine extends BaseExtension {
|
|||||||
EngineManager.instance().register(this)
|
EngineManager.instance().register(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async registerModels(models: Model[]): Promise<void> {
|
||||||
|
const modelFolderPath = await joinPath([await getJanDataFolderPath(), AIEngine.modelsFolder])
|
||||||
|
|
||||||
|
let shouldNotifyModelUpdate = false
|
||||||
|
for (const model of models) {
|
||||||
|
const modelPath = await joinPath([modelFolderPath, model.id])
|
||||||
|
const isExist = await fs.existsSync(modelPath)
|
||||||
|
|
||||||
|
if (isExist) {
|
||||||
|
await this.migrateModelIfNeeded(model, modelPath)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.mkdir(modelPath)
|
||||||
|
await fs.writeFileSync(
|
||||||
|
await joinPath([modelPath, 'model.json']),
|
||||||
|
JSON.stringify(model, null, 2)
|
||||||
|
)
|
||||||
|
shouldNotifyModelUpdate = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldNotifyModelUpdate) {
|
||||||
|
events.emit(ModelEvent.OnModelsUpdate, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async migrateModelIfNeeded(model: Model, modelPath: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const modelJson = await fs.readFileSync(await joinPath([modelPath, 'model.json']), 'utf-8')
|
||||||
|
const currentModel: Model = JSON.parse(modelJson)
|
||||||
|
if (currentModel.version !== model.version) {
|
||||||
|
await fs.writeFileSync(
|
||||||
|
await joinPath([modelPath, 'model.json']),
|
||||||
|
JSON.stringify(model, null, 2)
|
||||||
|
)
|
||||||
|
|
||||||
|
events.emit(ModelEvent.OnModelsUpdate, {})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error while try to migrating model', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the model.
|
* Loads the model.
|
||||||
*/
|
*/
|
||||||
@ -65,40 +101,4 @@ export abstract class AIEngine extends BaseExtension {
|
|||||||
* Stop inference
|
* Stop inference
|
||||||
*/
|
*/
|
||||||
stopInference() {}
|
stopInference() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pre-populate models to App Data Folder
|
|
||||||
*/
|
|
||||||
prePopulateModels(): Promise<void> {
|
|
||||||
const modelFolder = 'models'
|
|
||||||
return this.models().then((models) => {
|
|
||||||
const prePoluateOperations = models.map((model) =>
|
|
||||||
getJanDataFolderPath()
|
|
||||||
.then((janDataFolder) =>
|
|
||||||
// Attempt to create the model folder
|
|
||||||
joinPath([janDataFolder, modelFolder, model.id]).then((path) =>
|
|
||||||
fs
|
|
||||||
.mkdir(path)
|
|
||||||
.catch()
|
|
||||||
.then(() => path)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then((path) => joinPath([path, 'model.json']))
|
|
||||||
.then((path) => {
|
|
||||||
// Do not overwite existing model.json
|
|
||||||
return fs.existsSync(path).then((exist: any) => {
|
|
||||||
if (!exist) return fs.writeFileSync(path, JSON.stringify(model, null, 2))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
console.error('Error', e)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
Promise.all(prePoluateOperations).then(() =>
|
|
||||||
// Emit event to update models
|
|
||||||
// So the UI can update the models list
|
|
||||||
events.emit(ModelEvent.OnModelsUpdate, {})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
/*
|
/*
|
||||||
* Inference request
|
* Inference request
|
||||||
*/
|
*/
|
||||||
override inference(data: MessageRequest) {
|
override async inference(data: MessageRequest) {
|
||||||
if (data.model?.engine?.toString() !== this.provider) return
|
if (data.model?.engine?.toString() !== this.provider) return
|
||||||
|
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
@ -77,12 +77,14 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
...data.model,
|
...data.model,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const header = await this.headers()
|
||||||
|
|
||||||
requestInference(
|
requestInference(
|
||||||
this.inferenceUrl,
|
this.inferenceUrl,
|
||||||
data.messages ?? [],
|
data.messages ?? [],
|
||||||
model,
|
model,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.headers()
|
header
|
||||||
).subscribe({
|
).subscribe({
|
||||||
next: (content: any) => {
|
next: (content: any) => {
|
||||||
const messageContent: ThreadContent = {
|
const messageContent: ThreadContent = {
|
||||||
@ -100,7 +102,9 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
},
|
},
|
||||||
error: async (err: any) => {
|
error: async (err: any) => {
|
||||||
console.error(`Inference error: ${JSON.stringify(err, null, 2)}`)
|
console.debug('inference url: ', this.inferenceUrl)
|
||||||
|
console.debug('header: ', header)
|
||||||
|
console.error(`Inference error:`, JSON.stringify(err))
|
||||||
if (this.isCancelled || message.content.length) {
|
if (this.isCancelled || message.content.length) {
|
||||||
message.status = MessageStatus.Stopped
|
message.status = MessageStatus.Stopped
|
||||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
@ -131,7 +135,7 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
/**
|
/**
|
||||||
* Headers for the inference request
|
* Headers for the inference request
|
||||||
*/
|
*/
|
||||||
headers(): HeadersInit {
|
async headers(): Promise<HeadersInit> {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import { OAIEngine } from './OAIEngine'
|
|||||||
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||||
*/
|
*/
|
||||||
export abstract class RemoteOAIEngine extends OAIEngine {
|
export abstract class RemoteOAIEngine extends OAIEngine {
|
||||||
// The inference engine
|
apiKey?: string
|
||||||
abstract apiKey: string
|
|
||||||
/**
|
/**
|
||||||
* On extension load, subscribe to events.
|
* On extension load, subscribe to events.
|
||||||
*/
|
*/
|
||||||
@ -17,10 +16,12 @@ export abstract class RemoteOAIEngine extends OAIEngine {
|
|||||||
/**
|
/**
|
||||||
* Headers for the inference request
|
* Headers for the inference request
|
||||||
*/
|
*/
|
||||||
override headers(): HeadersInit {
|
override async headers(): Promise<HeadersInit> {
|
||||||
return {
|
return {
|
||||||
'Authorization': `Bearer ${this.apiKey}`,
|
...(this.apiKey && {
|
||||||
'api-key': `${this.apiKey}`,
|
'Authorization': `Bearer ${this.apiKey}`,
|
||||||
|
'api-key': `${this.apiKey}`,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,9 +36,15 @@ export function requestInference(
|
|||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
let errorCode = ErrorCode.Unknown;
|
||||||
|
if (data.error) {
|
||||||
|
errorCode = data.error.code ?? data.error.type ?? ErrorCode.Unknown
|
||||||
|
} else if (response.status === 401) {
|
||||||
|
errorCode = ErrorCode.InvalidApiKey;
|
||||||
|
}
|
||||||
const error = {
|
const error = {
|
||||||
message: data.error?.message ?? 'Error occurred.',
|
message: data.error?.message ?? 'Error occurred.',
|
||||||
code: data.error?.code ?? ErrorCode.Unknown,
|
code: errorCode,
|
||||||
}
|
}
|
||||||
subscriber.error(error)
|
subscriber.error(error)
|
||||||
subscriber.complete()
|
subscriber.complete()
|
||||||
@ -60,14 +66,20 @@ export function requestInference(
|
|||||||
}
|
}
|
||||||
const text = decoder.decode(value)
|
const text = decoder.decode(value)
|
||||||
const lines = text.trim().split('\n')
|
const lines = text.trim().split('\n')
|
||||||
|
let cachedLines = ''
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.startsWith('data: ') && !line.includes('data: [DONE]')) {
|
try {
|
||||||
const data = JSON.parse(line.replace('data: ', ''))
|
const toParse = cachedLines + line
|
||||||
content += data.choices[0]?.delta?.content ?? ''
|
if (!line.includes('data: [DONE]')) {
|
||||||
if (content.startsWith('assistant: ')) {
|
const data = JSON.parse(toParse.replace('data: ', ''))
|
||||||
content = content.replace('assistant: ', '')
|
content += data.choices[0]?.delta?.content ?? ''
|
||||||
|
if (content.startsWith('assistant: ')) {
|
||||||
|
content = content.replace('assistant: ', '')
|
||||||
|
}
|
||||||
|
if (content !== '') subscriber.next(content)
|
||||||
}
|
}
|
||||||
subscriber.next(content)
|
} catch {
|
||||||
|
cachedLines = line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export abstract class MonitoringExtension extends BaseExtension implements Monit
|
|||||||
return ExtensionTypeEnum.SystemMonitoring
|
return ExtensionTypeEnum.SystemMonitoring
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract getGpuSetting(): Promise<GpuSetting>
|
abstract getGpuSetting(): Promise<GpuSetting | undefined>
|
||||||
abstract getResourcesInfo(): Promise<any>
|
abstract getResourcesInfo(): Promise<any>
|
||||||
abstract getCurrentLoad(): Promise<any>
|
abstract getCurrentLoad(): Promise<any>
|
||||||
abstract getOsInfo(): Promise<OperatingSystemInfo>
|
abstract getOsInfo(): Promise<OperatingSystemInfo>
|
||||||
|
|||||||
@ -5,19 +5,16 @@ export type Handler = (route: string, args: any) => any
|
|||||||
|
|
||||||
export class RequestHandler {
|
export class RequestHandler {
|
||||||
handler: Handler
|
handler: Handler
|
||||||
adataper: RequestAdapter
|
adapter: RequestAdapter
|
||||||
|
|
||||||
constructor(handler: Handler, observer?: Function) {
|
constructor(handler: Handler, observer?: Function) {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
this.adataper = new RequestAdapter(observer)
|
this.adapter = new RequestAdapter(observer)
|
||||||
}
|
}
|
||||||
|
|
||||||
handle() {
|
handle() {
|
||||||
CoreRoutes.map((route) => {
|
CoreRoutes.map((route) => {
|
||||||
this.handler(route, async (...args: any[]) => {
|
this.handler(route, async (...args: any[]) => this.adapter.process(route, ...args))
|
||||||
const values = await this.adataper.process(route, ...args)
|
|
||||||
return values
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { basename, isAbsolute, join, relative } from 'path'
|
import { basename, isAbsolute, join, relative } from 'path'
|
||||||
|
|
||||||
import { Processor } from './Processor'
|
import { Processor } from './Processor'
|
||||||
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
import {
|
||||||
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
log as writeLog,
|
||||||
import { appResourcePath } from '../../helper/path'
|
appResourcePath,
|
||||||
|
getAppConfigurations as appConfiguration,
|
||||||
|
updateAppConfiguration,
|
||||||
|
} from '../../helper'
|
||||||
|
|
||||||
export class App implements Processor {
|
export class App implements Processor {
|
||||||
observer?: Function
|
observer?: Function
|
||||||
@ -56,13 +59,6 @@ export class App implements Processor {
|
|||||||
writeLog(args)
|
writeLog(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Log message to log file.
|
|
||||||
*/
|
|
||||||
logServer(args: any) {
|
|
||||||
writeServerLog(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAppConfigurations() {
|
getAppConfigurations() {
|
||||||
return appConfiguration()
|
return appConfiguration()
|
||||||
}
|
}
|
||||||
@ -83,6 +79,7 @@ export class App implements Processor {
|
|||||||
isVerboseEnabled: args?.isVerboseEnabled,
|
isVerboseEnabled: args?.isVerboseEnabled,
|
||||||
schemaPath: join(await appResourcePath(), 'docs', 'openapi', 'jan.yaml'),
|
schemaPath: join(await appResourcePath(), 'docs', 'openapi', 'jan.yaml'),
|
||||||
baseDir: join(await appResourcePath(), 'docs', 'openapi'),
|
baseDir: join(await appResourcePath(), 'docs', 'openapi'),
|
||||||
|
prefix: args?.prefix,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -316,6 +316,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestedModel = matchedModels[0]
|
const requestedModel = matchedModels[0]
|
||||||
|
|
||||||
const engineConfiguration = await getEngineConfiguration(requestedModel.engine)
|
const engineConfiguration = await getEngineConfiguration(requestedModel.engine)
|
||||||
|
|
||||||
let apiKey: string | undefined = undefined
|
let apiKey: string | undefined = undefined
|
||||||
@ -323,7 +324,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
|
|
||||||
if (engineConfiguration) {
|
if (engineConfiguration) {
|
||||||
apiKey = engineConfiguration.api_key
|
apiKey = engineConfiguration.api_key
|
||||||
apiUrl = engineConfiguration.full_url
|
apiUrl = engineConfiguration.full_url ?? DEFAULT_CHAT_COMPLETION_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: Record<string, any> = {
|
const headers: Record<string, any> = {
|
||||||
@ -334,7 +335,6 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
headers['Authorization'] = `Bearer ${apiKey}`
|
headers['Authorization'] = `Bearer ${apiKey}`
|
||||||
headers['api-key'] = apiKey
|
headers['api-key'] = apiKey
|
||||||
}
|
}
|
||||||
console.debug(apiUrl)
|
|
||||||
|
|
||||||
if (requestedModel.engine === 'openai' && request.body.stop) {
|
if (requestedModel.engine === 'openai' && request.body.stop) {
|
||||||
// openai only allows max 4 stop words
|
// openai only allows max 4 stop words
|
||||||
@ -352,7 +352,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
|||||||
reply.code(400).send(response)
|
reply.code(400).send(response)
|
||||||
} else {
|
} else {
|
||||||
reply.raw.writeHead(200, {
|
reply.raw.writeHead(200, {
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': request.body.stream === true ? 'text/event-stream' : 'application/json',
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Connection': 'keep-alive',
|
'Connection': 'keep-alive',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
import {
|
||||||
import { logServer } from '../../../helper/log'
|
getJanDataFolderPath,
|
||||||
|
getJanExtensionsPath,
|
||||||
|
getSystemResourceInfo,
|
||||||
|
log,
|
||||||
|
} from '../../../helper'
|
||||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||||
import {
|
import {
|
||||||
@ -69,7 +73,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
logServer(`[NITRO]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
log(`[SERVER]::Debug: Nitro model settings: ${JSON.stringify(nitroModelSettings)}`)
|
||||||
|
|
||||||
// Convert settings.prompt_template to system_prompt, user_prompt, ai_prompt
|
// Convert settings.prompt_template to system_prompt, user_prompt, ai_prompt
|
||||||
if (modelMetadata.settings.prompt_template) {
|
if (modelMetadata.settings.prompt_template) {
|
||||||
@ -140,7 +144,7 @@ const runNitroAndLoadModel = async (modelId: string, modelSettings: NitroModelSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
const spawnNitroProcess = async (): Promise<void> => {
|
const spawnNitroProcess = async (): Promise<void> => {
|
||||||
logServer(`[NITRO]::Debug: Spawning Nitro subprocess...`)
|
log(`[SERVER]::Debug: Spawning Nitro subprocess...`)
|
||||||
|
|
||||||
let binaryFolder = join(
|
let binaryFolder = join(
|
||||||
getJanExtensionsPath(),
|
getJanExtensionsPath(),
|
||||||
@ -155,8 +159,8 @@ const spawnNitroProcess = async (): Promise<void> => {
|
|||||||
|
|
||||||
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
||||||
// Execute the binary
|
// Execute the binary
|
||||||
logServer(
|
log(
|
||||||
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
`[SERVER]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||||
)
|
)
|
||||||
subprocess = spawn(
|
subprocess = spawn(
|
||||||
executableOptions.executablePath,
|
executableOptions.executablePath,
|
||||||
@ -172,20 +176,20 @@ const spawnNitroProcess = async (): Promise<void> => {
|
|||||||
|
|
||||||
// Handle subprocess output
|
// Handle subprocess output
|
||||||
subprocess.stdout.on('data', (data: any) => {
|
subprocess.stdout.on('data', (data: any) => {
|
||||||
logServer(`[NITRO]::Debug: ${data}`)
|
log(`[SERVER]::Debug: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
subprocess.stderr.on('data', (data: any) => {
|
subprocess.stderr.on('data', (data: any) => {
|
||||||
logServer(`[NITRO]::Error: ${data}`)
|
log(`[SERVER]::Error: ${data}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
subprocess.on('close', (code: any) => {
|
subprocess.on('close', (code: any) => {
|
||||||
logServer(`[NITRO]::Debug: Nitro exited with code: ${code}`)
|
log(`[SERVER]::Debug: Nitro exited with code: ${code}`)
|
||||||
subprocess = undefined
|
subprocess = undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
tcpPortUsed.waitUntilUsed(NITRO_DEFAULT_PORT, 300, 30000).then(() => {
|
tcpPortUsed.waitUntilUsed(NITRO_DEFAULT_PORT, 300, 30000).then(() => {
|
||||||
logServer(`[NITRO]::Debug: Nitro is ready`)
|
log(`[SERVER]::Debug: Nitro is ready`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,13 +231,9 @@ const executableNitroFile = (): NitroExecutableOptions => {
|
|||||||
binaryName = 'nitro.exe'
|
binaryName = 'nitro.exe'
|
||||||
} else if (process.platform === 'darwin') {
|
} else if (process.platform === 'darwin') {
|
||||||
/**
|
/**
|
||||||
* For MacOS: mac-arm64 (Silicon), mac-x64 (InteL)
|
* For MacOS: mac-universal both Silicon and InteL
|
||||||
*/
|
*/
|
||||||
if (process.arch === 'arm64') {
|
binaryFolder = join(binaryFolder, 'mac-universal')
|
||||||
binaryFolder = join(binaryFolder, 'mac-arm64')
|
|
||||||
} else {
|
|
||||||
binaryFolder = join(binaryFolder, 'mac-x64')
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* For Linux: linux-cpu, linux-cuda-11-7, linux-cuda-12-0
|
* For Linux: linux-cpu, linux-cuda-11-7, linux-cuda-12-0
|
||||||
@ -271,7 +271,7 @@ const validateModelStatus = async (): Promise<void> => {
|
|||||||
retries: 5,
|
retries: 5,
|
||||||
retryDelay: 500,
|
retryDelay: 500,
|
||||||
}).then(async (res: Response) => {
|
}).then(async (res: Response) => {
|
||||||
logServer(`[NITRO]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
log(`[SERVER]::Debug: Validate model state success with response ${JSON.stringify(res)}`)
|
||||||
// If the response is OK, check model_loaded status.
|
// If the response is OK, check model_loaded status.
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const body = await res.json()
|
const body = await res.json()
|
||||||
@ -286,7 +286,7 @@ const validateModelStatus = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> => {
|
const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> => {
|
||||||
logServer(`[NITRO]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
log(`[SERVER]::Debug: Loading model with params ${JSON.stringify(settings)}`)
|
||||||
const fetchRT = require('fetch-retry')
|
const fetchRT = require('fetch-retry')
|
||||||
const fetchRetry = fetchRT(fetch)
|
const fetchRetry = fetchRT(fetch)
|
||||||
|
|
||||||
@ -300,11 +300,11 @@ const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> =>
|
|||||||
retryDelay: 500,
|
retryDelay: 500,
|
||||||
})
|
})
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
logServer(`[NITRO]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
log(`[SERVER]::Debug: Load model success with response ${JSON.stringify(res)}`)
|
||||||
return Promise.resolve(res)
|
return Promise.resolve(res)
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
logServer(`[NITRO]::Error: Load model failed with error ${err}`)
|
log(`[SERVER]::Error: Load model failed with error ${err}`)
|
||||||
return Promise.reject(err)
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -327,7 +327,7 @@ export const stopModel = async (_modelId: string) => {
|
|||||||
})
|
})
|
||||||
}, 5000)
|
}, 5000)
|
||||||
const tcpPortUsed = require('tcp-port-used')
|
const tcpPortUsed = require('tcp-port-used')
|
||||||
logServer(`[NITRO]::Debug: Request to kill Nitro`)
|
log(`[SERVER]::Debug: Request to kill Nitro`)
|
||||||
|
|
||||||
fetch(NITRO_HTTP_KILL_URL, {
|
fetch(NITRO_HTTP_KILL_URL, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
@ -341,7 +341,7 @@ export const stopModel = async (_modelId: string) => {
|
|||||||
// don't need to do anything, we still kill the subprocess
|
// don't need to do anything, we still kill the subprocess
|
||||||
})
|
})
|
||||||
.then(() => tcpPortUsed.waitUntilFree(NITRO_DEFAULT_PORT, 300, 5000))
|
.then(() => tcpPortUsed.waitUntilFree(NITRO_DEFAULT_PORT, 300, 5000))
|
||||||
.then(() => logServer(`[NITRO]::Debug: Nitro process is terminated`))
|
.then(() => log(`[SERVER]::Debug: Nitro process is terminated`))
|
||||||
.then(() =>
|
.then(() =>
|
||||||
resolve({
|
resolve({
|
||||||
message: 'Model stopped',
|
message: 'Model stopped',
|
||||||
|
|||||||
@ -11,6 +11,7 @@ export default class Extension {
|
|||||||
* @property {string} origin Original specification provided to fetch the package.
|
* @property {string} origin Original specification provided to fetch the package.
|
||||||
* @property {Object} installOptions Options provided to pacote when fetching the manifest.
|
* @property {Object} installOptions Options provided to pacote when fetching the manifest.
|
||||||
* @property {name} name The name of the extension as defined in the manifest.
|
* @property {name} name The name of the extension as defined in the manifest.
|
||||||
|
* @property {name} productName The display name of the extension as defined in the manifest.
|
||||||
* @property {string} url Electron URL where the package can be accessed.
|
* @property {string} url Electron URL where the package can be accessed.
|
||||||
* @property {string} version Version of the package as defined in the manifest.
|
* @property {string} version Version of the package as defined in the manifest.
|
||||||
* @property {string} main The entry point as defined in the main entry of the manifest.
|
* @property {string} main The entry point as defined in the main entry of the manifest.
|
||||||
@ -19,6 +20,7 @@ export default class Extension {
|
|||||||
origin?: string
|
origin?: string
|
||||||
installOptions: any
|
installOptions: any
|
||||||
name?: string
|
name?: string
|
||||||
|
productName?: string
|
||||||
url?: string
|
url?: string
|
||||||
version?: string
|
version?: string
|
||||||
main?: string
|
main?: string
|
||||||
@ -42,7 +44,7 @@ export default class Extension {
|
|||||||
const Arborist = require('@npmcli/arborist')
|
const Arborist = require('@npmcli/arborist')
|
||||||
const defaultOpts = {
|
const defaultOpts = {
|
||||||
version: false,
|
version: false,
|
||||||
fullMetadata: false,
|
fullMetadata: true,
|
||||||
Arborist,
|
Arborist,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +79,7 @@ export default class Extension {
|
|||||||
return pacote.manifest(this.specifier, this.installOptions).then((mnf) => {
|
return pacote.manifest(this.specifier, this.installOptions).then((mnf) => {
|
||||||
// set the Package properties based on the it's manifest
|
// set the Package properties based on the it's manifest
|
||||||
this.name = mnf.name
|
this.name = mnf.name
|
||||||
|
this.productName = mnf.productName as string | undefined
|
||||||
this.version = mnf.version
|
this.version = mnf.version
|
||||||
this.main = mnf.main
|
this.main = mnf.main
|
||||||
this.description = mnf.description
|
this.description = mnf.description
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { AppConfiguration } from '../../types'
|
import { AppConfiguration, SettingComponentProps } from '../../types'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
@ -125,40 +125,32 @@ const exec = async (command: string): Promise<string> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a hacky way to get the api key. we should comes up with a better
|
||||||
|
// way to handle this
|
||||||
export const getEngineConfiguration = async (engineId: string) => {
|
export const getEngineConfiguration = async (engineId: string) => {
|
||||||
if (engineId !== 'openai' && engineId !== 'groq') {
|
if (engineId !== 'openai' && engineId !== 'groq') return undefined
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
|
||||||
const filePath = join(directoryPath, `${engineId}.json`)
|
|
||||||
const data = fs.readFileSync(filePath, 'utf-8')
|
|
||||||
return JSON.parse(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const settingDirectoryPath = join(
|
||||||
* Utility function to get server log path
|
getJanDataFolderPath(),
|
||||||
*
|
'settings',
|
||||||
* @returns {string} The log path.
|
'@janhq',
|
||||||
*/
|
engineId === 'openai' ? 'inference-openai-extension' : 'inference-groq-extension',
|
||||||
export const getServerLogPath = (): string => {
|
'settings.json'
|
||||||
const appConfigurations = getAppConfigurations()
|
)
|
||||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
|
||||||
if (!fs.existsSync(logFolderPath)) {
|
|
||||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
|
||||||
}
|
|
||||||
return join(logFolderPath, 'server.log')
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const content = fs.readFileSync(settingDirectoryPath, 'utf-8')
|
||||||
* Utility function to get app log path
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
*
|
const apiKeyId = engineId === 'openai' ? 'openai-api-key' : 'groq-api-key'
|
||||||
* @returns {string} The log path.
|
const keySetting = settings.find((setting) => setting.key === apiKeyId)
|
||||||
*/
|
let fullUrl = settings.find((setting) => setting.key === 'chat-completions-endpoint')
|
||||||
export const getAppLogPath = (): string => {
|
?.controllerProps.value
|
||||||
const appConfigurations = getAppConfigurations()
|
|
||||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
let apiKey = keySetting?.controllerProps.value
|
||||||
if (!fs.existsSync(logFolderPath)) {
|
if (typeof apiKey !== 'string') apiKey = ''
|
||||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
if (typeof fullUrl !== 'string') fullUrl = ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
api_key: apiKey,
|
||||||
|
full_url: fullUrl,
|
||||||
}
|
}
|
||||||
return join(logFolderPath, 'app.log')
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
export * from './config'
|
export * from './config'
|
||||||
export * from './download'
|
export * from './download'
|
||||||
export * from './log'
|
export * from './logger'
|
||||||
export * from './module'
|
export * from './module'
|
||||||
export * from './path'
|
export * from './path'
|
||||||
export * from './resource'
|
export * from './resource'
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
import fs from 'fs'
|
|
||||||
import util from 'util'
|
|
||||||
import { getAppLogPath, getServerLogPath } from './config'
|
|
||||||
|
|
||||||
export const log = (message: string) => {
|
|
||||||
const path = getAppLogPath()
|
|
||||||
if (!message.startsWith('[')) {
|
|
||||||
message = `[APP]::${message}`
|
|
||||||
}
|
|
||||||
|
|
||||||
message = `${new Date().toISOString()} ${message}`
|
|
||||||
|
|
||||||
writeLog(message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const logServer = (message: string) => {
|
|
||||||
const path = getServerLogPath()
|
|
||||||
if (!message.startsWith('[')) {
|
|
||||||
message = `[SERVER]::${message}`
|
|
||||||
}
|
|
||||||
|
|
||||||
message = `${new Date().toISOString()} ${message}`
|
|
||||||
writeLog(message, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
const writeLog = (message: string, logPath: string) => {
|
|
||||||
if (!fs.existsSync(logPath)) {
|
|
||||||
fs.writeFileSync(logPath, message)
|
|
||||||
} else {
|
|
||||||
const logFile = fs.createWriteStream(logPath, {
|
|
||||||
flags: 'a',
|
|
||||||
})
|
|
||||||
logFile.write(util.format(message) + '\n')
|
|
||||||
logFile.close()
|
|
||||||
console.debug(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
81
core/src/node/helper/logger.ts
Normal file
81
core/src/node/helper/logger.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Abstract Logger class that all loggers should extend.
|
||||||
|
export abstract class Logger {
|
||||||
|
// Each logger must have a unique name.
|
||||||
|
abstract name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log message to log file.
|
||||||
|
* This method should be overridden by subclasses to provide specific logging behavior.
|
||||||
|
*/
|
||||||
|
abstract log(args: any): void
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerManager is a singleton class that manages all registered loggers.
|
||||||
|
export class LoggerManager {
|
||||||
|
// Map of registered loggers, keyed by their names.
|
||||||
|
public loggers = new Map<string, Logger>()
|
||||||
|
|
||||||
|
// Array to store logs that are queued before the loggers are registered.
|
||||||
|
queuedLogs: any[] = []
|
||||||
|
|
||||||
|
// Flag to indicate whether flushLogs is currently running.
|
||||||
|
private isFlushing = false
|
||||||
|
|
||||||
|
// Register a new logger. If a logger with the same name already exists, it will be replaced.
|
||||||
|
register(logger: Logger) {
|
||||||
|
this.loggers.set(logger.name, logger)
|
||||||
|
}
|
||||||
|
// Unregister a logger by its name.
|
||||||
|
unregister(name: string) {
|
||||||
|
this.loggers.delete(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name: string) {
|
||||||
|
return this.loggers.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush queued logs to all registered loggers.
|
||||||
|
flushLogs() {
|
||||||
|
// If flushLogs is already running, do nothing.
|
||||||
|
if (this.isFlushing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFlushing = true
|
||||||
|
|
||||||
|
while (this.queuedLogs.length > 0 && this.loggers.size > 0) {
|
||||||
|
const log = this.queuedLogs.shift()
|
||||||
|
this.loggers.forEach((logger) => {
|
||||||
|
logger.log(log)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isFlushing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log message using all registered loggers.
|
||||||
|
log(args: any) {
|
||||||
|
this.queuedLogs.push(args)
|
||||||
|
|
||||||
|
this.flushLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The instance of the logger.
|
||||||
|
* If an instance doesn't exist, it creates a new one.
|
||||||
|
* This ensures that there is only one LoggerManager instance at any time.
|
||||||
|
*/
|
||||||
|
static instance(): LoggerManager {
|
||||||
|
let instance: LoggerManager | undefined = global.core?.logger
|
||||||
|
if (!instance) {
|
||||||
|
instance = new LoggerManager()
|
||||||
|
if (!global.core) global.core = {}
|
||||||
|
global.core.logger = instance
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const log = (...args: any) => {
|
||||||
|
LoggerManager.instance().log(args)
|
||||||
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
import { SystemResourceInfo } from '../../types'
|
import { SystemResourceInfo } from '../../types'
|
||||||
import { physicalCpuCount } from './config'
|
import { physicalCpuCount } from './config'
|
||||||
import { log } from './log'
|
import { log } from './logger'
|
||||||
|
|
||||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||||
const cpu = await physicalCpuCount()
|
const cpu = await physicalCpuCount()
|
||||||
const message = `[NITRO]::CPU informations - ${cpu}`
|
log(`[NITRO]::CPU informations - ${cpu}`)
|
||||||
log(message)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
numCpuPhysicalCore: cpu,
|
numCpuPhysicalCore: cpu,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export enum NativeRoute {
|
|||||||
openAppDirectory = 'openAppDirectory',
|
openAppDirectory = 'openAppDirectory',
|
||||||
openFileExplore = 'openFileExplorer',
|
openFileExplore = 'openFileExplorer',
|
||||||
selectDirectory = 'selectDirectory',
|
selectDirectory = 'selectDirectory',
|
||||||
selectModelFiles = 'selectModelFiles',
|
selectFiles = 'selectFiles',
|
||||||
relaunch = 'relaunch',
|
relaunch = 'relaunch',
|
||||||
|
|
||||||
hideQuickAskWindow = 'hideQuickAskWindow',
|
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||||
|
|||||||
@ -9,3 +9,4 @@ export * from './config'
|
|||||||
export * from './huggingface'
|
export * from './huggingface'
|
||||||
export * from './miscellaneous'
|
export * from './miscellaneous'
|
||||||
export * from './api'
|
export * from './api'
|
||||||
|
export * from './setting'
|
||||||
|
|||||||
@ -85,6 +85,8 @@ export enum ErrorCode {
|
|||||||
|
|
||||||
InsufficientQuota = 'insufficient_quota',
|
InsufficientQuota = 'insufficient_quota',
|
||||||
|
|
||||||
|
InvalidRequestError = 'invalid_request_error',
|
||||||
|
|
||||||
Unknown = 'unknown',
|
Unknown = 'unknown',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,4 +2,5 @@ export * from './systemResourceInfo'
|
|||||||
export * from './promptTemplate'
|
export * from './promptTemplate'
|
||||||
export * from './appUpdate'
|
export * from './appUpdate'
|
||||||
export * from './fileDownloadRequest'
|
export * from './fileDownloadRequest'
|
||||||
export * from './networkConfig'
|
export * from './networkConfig'
|
||||||
|
export * from './selectFiles'
|
||||||
|
|||||||
37
core/src/types/miscellaneous/selectFiles.ts
Normal file
37
core/src/types/miscellaneous/selectFiles.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export type SelectFileOption = {
|
||||||
|
/**
|
||||||
|
* The title of the dialog.
|
||||||
|
*/
|
||||||
|
title?: string
|
||||||
|
/**
|
||||||
|
* Whether the dialog allows multiple selection.
|
||||||
|
*/
|
||||||
|
allowMultiple?: boolean
|
||||||
|
|
||||||
|
buttonLabel?: string
|
||||||
|
|
||||||
|
selectDirectory?: boolean
|
||||||
|
|
||||||
|
props?: SelectFileProp[]
|
||||||
|
|
||||||
|
filters?: FilterOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FilterOption = {
|
||||||
|
name: string
|
||||||
|
extensions: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SelectFilePropTuple = [
|
||||||
|
'openFile',
|
||||||
|
'openDirectory',
|
||||||
|
'multiSelections',
|
||||||
|
'showHiddenFiles',
|
||||||
|
'createDirectory',
|
||||||
|
'promptToCreate',
|
||||||
|
'noResolveAliases',
|
||||||
|
'treatPackageAsDirectory',
|
||||||
|
'dontAddToRecent',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export type SelectFileProp = (typeof SelectFilePropTuple)[number]
|
||||||
@ -32,7 +32,7 @@ export type GpuSettingInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SystemInformation = {
|
export type SystemInformation = {
|
||||||
gpuSetting: GpuSetting
|
gpuSetting?: GpuSetting
|
||||||
osInfo?: OperatingSystemInfo
|
osInfo?: OperatingSystemInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ export type Model = {
|
|||||||
/**
|
/**
|
||||||
* The version of the model.
|
* The version of the model.
|
||||||
*/
|
*/
|
||||||
version: number
|
version: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The format of the model.
|
* The format of the model.
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { GpuSetting, OperatingSystemInfo } from '../miscellaneous'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitoring extension for system monitoring.
|
* Monitoring extension for system monitoring.
|
||||||
* @extends BaseExtension
|
* @extends BaseExtension
|
||||||
@ -14,4 +16,14 @@ export interface MonitoringInterface {
|
|||||||
* @returns {Promise<any>} A promise that resolves with the current system load.
|
* @returns {Promise<any>} A promise that resolves with the current system load.
|
||||||
*/
|
*/
|
||||||
getCurrentLoad(): Promise<any>
|
getCurrentLoad(): Promise<any>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the GPU configuration.
|
||||||
|
*/
|
||||||
|
getGpuSetting(): Promise<GpuSetting | undefined>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns information about the operating system.
|
||||||
|
*/
|
||||||
|
getOsInfo(): Promise<OperatingSystemInfo>
|
||||||
}
|
}
|
||||||
|
|||||||
1
core/src/types/setting/index.ts
Normal file
1
core/src/types/setting/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './settingComponent'
|
||||||
34
core/src/types/setting/settingComponent.ts
Normal file
34
core/src/types/setting/settingComponent.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export type SettingComponentProps = {
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
controllerType: ControllerType
|
||||||
|
controllerProps: SliderComponentProps | CheckboxComponentProps | InputComponentProps
|
||||||
|
|
||||||
|
extensionName?: string
|
||||||
|
requireModelReload?: boolean
|
||||||
|
configType?: ConfigType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ConfigType = 'runtime' | 'setting'
|
||||||
|
|
||||||
|
export type ControllerType = 'slider' | 'checkbox' | 'input'
|
||||||
|
|
||||||
|
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
||||||
|
|
||||||
|
export type InputComponentProps = {
|
||||||
|
placeholder: string
|
||||||
|
value: string
|
||||||
|
type?: InputType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SliderComponentProps = {
|
||||||
|
min: number
|
||||||
|
max: number
|
||||||
|
step: number
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CheckboxComponentProps = {
|
||||||
|
value: boolean
|
||||||
|
}
|
||||||
171
docker-compose-dev.yml
Normal file
171
docker-compose-dev.yml
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# Docker Compose file for setting up Minio, createbuckets, app_cpu, and app_gpu services
|
||||||
|
|
||||||
|
version: '3.7'
|
||||||
|
|
||||||
|
services:
|
||||||
|
# Minio service for object storage
|
||||||
|
minio:
|
||||||
|
image: minio/minio
|
||||||
|
volumes:
|
||||||
|
- minio_data:/data
|
||||||
|
ports:
|
||||||
|
- '9000:9000'
|
||||||
|
- '9001:9001'
|
||||||
|
environment:
|
||||||
|
# Set the root user and password for Minio
|
||||||
|
MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY
|
||||||
|
MINIO_ROOT_PASSWORD: minioadmin # This acts as AWS_SECRET_ACCESS_KEY
|
||||||
|
command: server --console-address ":9001" /data
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
ipv4_address: 10.5.0.2
|
||||||
|
|
||||||
|
# createbuckets service to create a bucket and set its policy
|
||||||
|
createbuckets:
|
||||||
|
image: minio/mc
|
||||||
|
depends_on:
|
||||||
|
- minio
|
||||||
|
entrypoint: >
|
||||||
|
/bin/sh -c "
|
||||||
|
/usr/bin/mc alias set myminio http://minio:9000 minioadmin minioadmin;
|
||||||
|
/usr/bin/mc mb myminio/mybucket;
|
||||||
|
/usr/bin/mc policy set public myminio/mybucket;
|
||||||
|
exit 0;
|
||||||
|
"
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
|
||||||
|
# app_cpu service for running the CPU version of the application
|
||||||
|
app_cpu_s3fs:
|
||||||
|
image: jan:latest
|
||||||
|
volumes:
|
||||||
|
- app_data_cpu_s3fs:/app/server/build/jan
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_cpu
|
||||||
|
AWS_ACCESS_KEY_ID: minioadmin
|
||||||
|
AWS_SECRET_ACCESS_KEY: minioadmin
|
||||||
|
S3_BUCKET_NAME: mybucket
|
||||||
|
AWS_ENDPOINT: http://10.5.0.2:9000
|
||||||
|
AWS_REGION: us-east-1
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
|
restart: always
|
||||||
|
profiles:
|
||||||
|
- cpu-s3fs
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
- '1337:1337'
|
||||||
|
- '3928:3928'
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
ipv4_address: 10.5.0.3
|
||||||
|
|
||||||
|
# app_gpu service for running the GPU version of the application
|
||||||
|
app_gpu_s3fs:
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: all
|
||||||
|
capabilities: [gpu]
|
||||||
|
image: jan-gpu:latest
|
||||||
|
volumes:
|
||||||
|
- app_data_gpu_s3fs:/app/server/build/jan
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.gpu
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_gpu
|
||||||
|
AWS_ACCESS_KEY_ID: minioadmin
|
||||||
|
AWS_SECRET_ACCESS_KEY: minioadmin
|
||||||
|
S3_BUCKET_NAME: mybucket
|
||||||
|
AWS_ENDPOINT: http://10.5.0.2:9000
|
||||||
|
AWS_REGION: us-east-1
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
|
profiles:
|
||||||
|
- gpu-s3fs
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
- '1337:1337'
|
||||||
|
- '3928:3928'
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
ipv4_address: 10.5.0.4
|
||||||
|
|
||||||
|
app_cpu_fs:
|
||||||
|
image: jan:latest
|
||||||
|
volumes:
|
||||||
|
- app_data_cpu_fs:/app/server/build/jan
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
|
restart: always
|
||||||
|
profiles:
|
||||||
|
- cpu-fs
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
- '1337:1337'
|
||||||
|
- '3928:3928'
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
ipv4_address: 10.5.0.5
|
||||||
|
|
||||||
|
# app_gpu service for running the GPU version of the application
|
||||||
|
app_gpu_fs:
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: all
|
||||||
|
capabilities: [gpu]
|
||||||
|
image: jan-gpu:latest
|
||||||
|
volumes:
|
||||||
|
- app_data_gpu_fs:/app/server/build/jan
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.gpu
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
API_BASE_URL: http://localhost:1337
|
||||||
|
profiles:
|
||||||
|
- gpu-fs
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
- '1337:1337'
|
||||||
|
- '3928:3928'
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
ipv4_address: 10.5.0.6
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
minio_data:
|
||||||
|
app_data_cpu_s3fs:
|
||||||
|
app_data_gpu_s3fs:
|
||||||
|
app_data_cpu_fs:
|
||||||
|
app_data_gpu_fs:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
vpcbr:
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 10.5.0.0/16
|
||||||
|
gateway: 10.5.0.1
|
||||||
|
# Usage:
|
||||||
|
# - Run 'docker compose -f docker-compose-dev.yml --profile cpu-s3fs up -d' to start the app_cpu service
|
||||||
|
# - Run 'docker compose -f docker-compose-dev.yml --profile gpu-s3fs up -d' to start the app_gpu service
|
||||||
|
# - Run 'docker compose -f docker-compose-dev.yml --profile cpu-fs up -d' to start the app_cpu service
|
||||||
|
# - Run 'docker compose -f docker-compose-dev.yml --profile gpu-fs up -d' to start the app_gpu service
|
||||||
@ -9,8 +9,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- minio_data:/data
|
- minio_data:/data
|
||||||
ports:
|
ports:
|
||||||
- "9000:9000"
|
- '9000:9000'
|
||||||
- "9001:9001"
|
- '9001:9001'
|
||||||
environment:
|
environment:
|
||||||
# Set the root user and password for Minio
|
# Set the root user and password for Minio
|
||||||
MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY
|
MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY
|
||||||
@ -18,7 +18,7 @@ services:
|
|||||||
command: server --console-address ":9001" /data
|
command: server --console-address ":9001" /data
|
||||||
restart: always
|
restart: always
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 20s
|
timeout: 20s
|
||||||
retries: 3
|
retries: 3
|
||||||
@ -43,12 +43,9 @@ services:
|
|||||||
|
|
||||||
# app_cpu service for running the CPU version of the application
|
# app_cpu service for running the CPU version of the application
|
||||||
app_cpu_s3fs:
|
app_cpu_s3fs:
|
||||||
image: jan:latest
|
|
||||||
volumes:
|
volumes:
|
||||||
- app_data_cpu_s3fs:/app/server/build/jan
|
- app_data_cpu_s3fs:/app/server/build/jan
|
||||||
build:
|
image: ghcr.io/janhq/jan-server:dev-cpu-latest
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
environment:
|
environment:
|
||||||
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_cpu
|
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_cpu
|
||||||
AWS_ACCESS_KEY_ID: minioadmin
|
AWS_ACCESS_KEY_ID: minioadmin
|
||||||
@ -61,9 +58,9 @@ services:
|
|||||||
profiles:
|
profiles:
|
||||||
- cpu-s3fs
|
- cpu-s3fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- '3000:3000'
|
||||||
- "1337:1337"
|
- '1337:1337'
|
||||||
- "3928:3928"
|
- '3928:3928'
|
||||||
networks:
|
networks:
|
||||||
vpcbr:
|
vpcbr:
|
||||||
ipv4_address: 10.5.0.3
|
ipv4_address: 10.5.0.3
|
||||||
@ -74,15 +71,12 @@ services:
|
|||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
devices:
|
devices:
|
||||||
- driver: nvidia
|
- driver: nvidia
|
||||||
count: all
|
count: all
|
||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
image: jan-gpu:latest
|
image: ghcr.io/janhq/jan-server:dev-cuda-12.2-latest
|
||||||
volumes:
|
volumes:
|
||||||
- app_data_gpu_s3fs:/app/server/build/jan
|
- app_data_gpu_s3fs:/app/server/build/jan
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.gpu
|
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_gpu
|
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_gpu
|
||||||
@ -95,29 +89,26 @@ services:
|
|||||||
profiles:
|
profiles:
|
||||||
- gpu-s3fs
|
- gpu-s3fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- '3000:3000'
|
||||||
- "1337:1337"
|
- '1337:1337'
|
||||||
- "3928:3928"
|
- '3928:3928'
|
||||||
networks:
|
networks:
|
||||||
vpcbr:
|
vpcbr:
|
||||||
ipv4_address: 10.5.0.4
|
ipv4_address: 10.5.0.4
|
||||||
|
|
||||||
app_cpu_fs:
|
app_cpu_fs:
|
||||||
image: jan:latest
|
image: ghcr.io/janhq/jan-server:dev-cpu-latest
|
||||||
volumes:
|
volumes:
|
||||||
- app_data_cpu_fs:/app/server/build/jan
|
- app_data_cpu_fs:/app/server/build/jan
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
environment:
|
environment:
|
||||||
API_BASE_URL: http://localhost:1337
|
API_BASE_URL: http://localhost:1337
|
||||||
restart: always
|
restart: always
|
||||||
profiles:
|
profiles:
|
||||||
- cpu-fs
|
- cpu-fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- '3000:3000'
|
||||||
- "1337:1337"
|
- '1337:1337'
|
||||||
- "3928:3928"
|
- '3928:3928'
|
||||||
networks:
|
networks:
|
||||||
vpcbr:
|
vpcbr:
|
||||||
ipv4_address: 10.5.0.5
|
ipv4_address: 10.5.0.5
|
||||||
@ -128,24 +119,21 @@ services:
|
|||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
devices:
|
devices:
|
||||||
- driver: nvidia
|
- driver: nvidia
|
||||||
count: all
|
count: all
|
||||||
capabilities: [gpu]
|
capabilities: [gpu]
|
||||||
image: jan-gpu:latest
|
image: ghcr.io/janhq/jan-server:dev-cuda-12.2-latest
|
||||||
volumes:
|
volumes:
|
||||||
- app_data_gpu_fs:/app/server/build/jan
|
- app_data_gpu_fs:/app/server/build/jan
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile.gpu
|
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
API_BASE_URL: http://localhost:1337
|
API_BASE_URL: http://localhost:1337
|
||||||
profiles:
|
profiles:
|
||||||
- gpu-fs
|
- gpu-fs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- '3000:3000'
|
||||||
- "1337:1337"
|
- '1337:1337'
|
||||||
- "3928:3928"
|
- '3928:3928'
|
||||||
networks:
|
networks:
|
||||||
vpcbr:
|
vpcbr:
|
||||||
ipv4_address: 10.5.0.6
|
ipv4_address: 10.5.0.6
|
||||||
@ -161,10 +149,9 @@ networks:
|
|||||||
vpcbr:
|
vpcbr:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
ipam:
|
ipam:
|
||||||
config:
|
config:
|
||||||
- subnet: 10.5.0.0/16
|
- subnet: 10.5.0.0/16
|
||||||
gateway: 10.5.0.1
|
gateway: 10.5.0.1
|
||||||
|
|
||||||
# Usage:
|
# Usage:
|
||||||
# - Run 'docker compose --profile cpu-s3fs up -d' to start the app_cpu service
|
# - Run 'docker compose --profile cpu-s3fs up -d' to start the app_cpu service
|
||||||
# - Run 'docker compose --profile gpu-s3fs up -d' to start the app_gpu service
|
# - Run 'docker compose --profile gpu-s3fs up -d' to start the app_gpu service
|
||||||
|
|||||||
@ -6,8 +6,11 @@ import {
|
|||||||
getJanDataFolderPath,
|
getJanDataFolderPath,
|
||||||
getJanExtensionsPath,
|
getJanExtensionsPath,
|
||||||
init,
|
init,
|
||||||
AppEvent, NativeRoute,
|
AppEvent,
|
||||||
|
NativeRoute,
|
||||||
|
SelectFileProp,
|
||||||
} from '@janhq/core/node'
|
} from '@janhq/core/node'
|
||||||
|
import { SelectFileOption } from '@janhq/core/.'
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
export function handleAppIPCs() {
|
||||||
/**
|
/**
|
||||||
@ -84,23 +87,39 @@ export function handleAppIPCs() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle(NativeRoute.selectModelFiles, async () => {
|
ipcMain.handle(
|
||||||
const mainWindow = windowManager.mainWindow
|
NativeRoute.selectFiles,
|
||||||
if (!mainWindow) {
|
async (_event, option?: SelectFileOption) => {
|
||||||
console.error('No main window found')
|
const mainWindow = windowManager.mainWindow
|
||||||
return
|
if (!mainWindow) {
|
||||||
}
|
console.error('No main window found')
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
return
|
||||||
title: 'Select model files',
|
}
|
||||||
buttonLabel: 'Select',
|
|
||||||
properties: ['openFile', 'openDirectory', 'multiSelections'],
|
|
||||||
})
|
|
||||||
if (canceled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return filePaths
|
const title = option?.title ?? 'Select files'
|
||||||
})
|
const buttonLabel = option?.buttonLabel ?? 'Select'
|
||||||
|
const props: SelectFileProp[] = ['openFile']
|
||||||
|
|
||||||
|
if (option?.allowMultiple) {
|
||||||
|
props.push('multiSelections')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option?.selectDirectory) {
|
||||||
|
props.push('openDirectory')
|
||||||
|
}
|
||||||
|
console.debug(`Select files with props: ${props}`)
|
||||||
|
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
title,
|
||||||
|
buttonLabel,
|
||||||
|
properties: props,
|
||||||
|
filters: option?.filters,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (canceled) return
|
||||||
|
|
||||||
|
return filePaths
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
NativeRoute.hideQuickAskWindow,
|
NativeRoute.hideQuickAskWindow,
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export function handleAppUpdates() {
|
|||||||
})
|
})
|
||||||
if (action.response === 0) {
|
if (action.response === 0) {
|
||||||
trayManager.destroyCurrentTray()
|
trayManager.destroyCurrentTray()
|
||||||
|
windowManager.closeQuickAskWindow()
|
||||||
waitingToInstallVersion = _info?.version
|
waitingToInstallVersion = _info?.version
|
||||||
autoUpdater.quitAndInstall()
|
autoUpdater.quitAndInstall()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { app, BrowserWindow, Tray } from 'electron'
|
import { app, BrowserWindow } from 'electron'
|
||||||
|
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
/**
|
/**
|
||||||
@ -11,7 +11,7 @@ import { getAppConfigurations, log } from '@janhq/core/node'
|
|||||||
* IPC Handlers
|
* IPC Handlers
|
||||||
**/
|
**/
|
||||||
import { injectHandler } from './handlers/common'
|
import { injectHandler } from './handlers/common'
|
||||||
import { handleAppUpdates, waitingToInstallVersion } from './handlers/update'
|
import { handleAppUpdates } from './handlers/update'
|
||||||
import { handleAppIPCs } from './handlers/native'
|
import { handleAppIPCs } from './handlers/native'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,11 +24,10 @@ import { cleanUpAndQuit } from './utils/clean'
|
|||||||
import { setupExtensions } from './utils/extension'
|
import { setupExtensions } from './utils/extension'
|
||||||
import { setupCore } from './utils/setup'
|
import { setupCore } from './utils/setup'
|
||||||
import { setupReactDevTool } from './utils/dev'
|
import { setupReactDevTool } from './utils/dev'
|
||||||
import { cleanLogs } from './utils/log'
|
|
||||||
|
|
||||||
import { registerShortcut } from './utils/selectedText'
|
|
||||||
import { trayManager } from './managers/tray'
|
import { trayManager } from './managers/tray'
|
||||||
import { logSystemInfo } from './utils/system'
|
import { logSystemInfo } from './utils/system'
|
||||||
|
import { registerGlobalShortcuts } from './utils/shortcut'
|
||||||
|
|
||||||
const preloadPath = join(__dirname, 'preload.js')
|
const preloadPath = join(__dirname, 'preload.js')
|
||||||
const rendererPath = join(__dirname, '..', 'renderer')
|
const rendererPath = join(__dirname, '..', 'renderer')
|
||||||
@ -38,8 +37,6 @@ const mainPath = join(rendererPath, 'index.html')
|
|||||||
const mainUrl = 'http://localhost:3000'
|
const mainUrl = 'http://localhost:3000'
|
||||||
const quickAskUrl = `${mainUrl}/search`
|
const quickAskUrl = `${mainUrl}/search`
|
||||||
|
|
||||||
const quickAskHotKey = 'CommandOrControl+J'
|
|
||||||
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock()
|
const gotTheLock = app.requestSingleInstanceLock()
|
||||||
|
|
||||||
app
|
app
|
||||||
@ -60,6 +57,7 @@ app
|
|||||||
.then(handleAppUpdates)
|
.then(handleAppUpdates)
|
||||||
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
|
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
|
||||||
.then(createMainWindow)
|
.then(createMainWindow)
|
||||||
|
.then(registerGlobalShortcuts)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
windowManager.mainWindow?.webContents.openDevTools()
|
windowManager.mainWindow?.webContents.openDevTools()
|
||||||
@ -76,16 +74,11 @@ app
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(() => cleanLogs())
|
|
||||||
|
|
||||||
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
||||||
windowManager.showMainWindow()
|
windowManager.showMainWindow()
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('ready', () => {
|
|
||||||
registerGlobalShortcuts()
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('before-quit', function (evt) {
|
app.on('before-quit', function (evt) {
|
||||||
trayManager.destroyCurrentTray()
|
trayManager.destroyCurrentTray()
|
||||||
})
|
})
|
||||||
@ -96,7 +89,11 @@ app.once('quit', () => {
|
|||||||
|
|
||||||
app.once('window-all-closed', () => {
|
app.once('window-all-closed', () => {
|
||||||
// Feature Toggle for Quick Ask
|
// Feature Toggle for Quick Ask
|
||||||
if (getAppConfigurations().quick_ask && !waitingToInstallVersion) return
|
if (
|
||||||
|
getAppConfigurations().quick_ask &&
|
||||||
|
!windowManager.isQuickAskWindowDestroyed()
|
||||||
|
)
|
||||||
|
return
|
||||||
cleanUpAndQuit()
|
cleanUpAndQuit()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -112,26 +109,6 @@ function createMainWindow() {
|
|||||||
windowManager.createMainWindow(preloadPath, startUrl)
|
windowManager.createMainWindow(preloadPath, startUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerGlobalShortcuts() {
|
|
||||||
const ret = registerShortcut(quickAskHotKey, (selectedText: string) => {
|
|
||||||
// Feature Toggle for Quick Ask
|
|
||||||
if (!getAppConfigurations().quick_ask) return
|
|
||||||
|
|
||||||
if (!windowManager.isQuickAskWindowVisible()) {
|
|
||||||
windowManager.showQuickAskWindow()
|
|
||||||
windowManager.sendQuickAskSelectedText(selectedText)
|
|
||||||
} else {
|
|
||||||
windowManager.hideQuickAskWindow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!ret) {
|
|
||||||
console.error('Global shortcut registration failed')
|
|
||||||
} else {
|
|
||||||
console.log('Global shortcut registered successfully')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles various IPC messages from the renderer process.
|
* Handles various IPC messages from the renderer process.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class WindowManager {
|
|||||||
windowManager.mainWindow?.on('close', function (evt) {
|
windowManager.mainWindow?.on('close', function (evt) {
|
||||||
// Feature Toggle for Quick Ask
|
// Feature Toggle for Quick Ask
|
||||||
if (!getAppConfigurations().quick_ask) return
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
|
||||||
if (!isAppQuitting) {
|
if (!isAppQuitting) {
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
windowManager.hideMainWindow()
|
windowManager.hideMainWindow()
|
||||||
@ -93,10 +93,22 @@ class WindowManager {
|
|||||||
this._quickAskWindowVisible = true
|
this._quickAskWindowVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeQuickAskWindow(): void {
|
||||||
|
if (this._quickAskWindow?.isDestroyed()) return
|
||||||
|
this._quickAskWindow?.close()
|
||||||
|
this._quickAskWindow?.destroy()
|
||||||
|
this._quickAskWindow = undefined
|
||||||
|
this._quickAskWindowVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
isQuickAskWindowVisible(): boolean {
|
isQuickAskWindowVisible(): boolean {
|
||||||
return this._quickAskWindowVisible
|
return this._quickAskWindowVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isQuickAskWindowDestroyed(): boolean {
|
||||||
|
return this._quickAskWindow?.isDestroyed() ?? true
|
||||||
|
}
|
||||||
|
|
||||||
expandQuickAskWindow(heightOffset: number): void {
|
expandQuickAskWindow(heightOffset: number): void {
|
||||||
const width = quickAskWindowConfig.width!
|
const width = quickAskWindowConfig.width!
|
||||||
const height = quickAskWindowConfig.height! + heightOffset
|
const height = quickAskWindowConfig.height! + heightOffset
|
||||||
@ -112,10 +124,18 @@ class WindowManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleanUp(): void {
|
cleanUp(): void {
|
||||||
this.mainWindow?.destroy()
|
if (!this.mainWindow?.isDestroyed()) {
|
||||||
this._quickAskWindow?.destroy()
|
this.mainWindow?.close()
|
||||||
this._quickAskWindowVisible = false
|
this.mainWindow?.destroy()
|
||||||
this._mainWindowVisible = false
|
this.mainWindow = undefined
|
||||||
|
this._mainWindowVisible = false
|
||||||
|
}
|
||||||
|
if (!this._quickAskWindow?.isDestroyed()) {
|
||||||
|
this._quickAskWindow?.close()
|
||||||
|
this._quickAskWindow?.destroy()
|
||||||
|
this._quickAskWindow = undefined
|
||||||
|
this._quickAskWindowVisible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,14 +14,12 @@
|
|||||||
"renderer/**/*",
|
"renderer/**/*",
|
||||||
"build/**/*.{js,map}",
|
"build/**/*.{js,map}",
|
||||||
"pre-install",
|
"pre-install",
|
||||||
"models/**/*",
|
|
||||||
"docs/**/*",
|
"docs/**/*",
|
||||||
"scripts/**/*",
|
"scripts/**/*",
|
||||||
"icons/**/*"
|
"icons/**/*"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"pre-install",
|
"pre-install",
|
||||||
"models",
|
|
||||||
"docs",
|
"docs",
|
||||||
"scripts",
|
"scripts",
|
||||||
"icons"
|
"icons"
|
||||||
@ -110,7 +108,8 @@
|
|||||||
"eslint-plugin-react": "^7.34.0",
|
"eslint-plugin-react": "^7.34.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"run-script-os": "^1.1.6",
|
"run-script-os": "^1.1.6",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"@reportportal/agent-js-playwright": "^5.1.7"
|
||||||
},
|
},
|
||||||
"installConfig": {
|
"installConfig": {
|
||||||
"hoistingLimits": "workspaces"
|
"hoistingLimits": "workspaces"
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
export function cleanLogs(
|
|
||||||
maxFileSizeBytes?: number | undefined,
|
|
||||||
daysToKeep?: number | undefined,
|
|
||||||
delayMs?: number | undefined
|
|
||||||
): void {
|
|
||||||
const size = maxFileSizeBytes ?? 1 * 1024 * 1024 // 1 MB
|
|
||||||
const days = daysToKeep ?? 7 // 7 days
|
|
||||||
const delays = delayMs ?? 10000 // 10 seconds
|
|
||||||
const logDirectory = path.join(getJanDataFolderPath(), 'logs')
|
|
||||||
|
|
||||||
// Perform log cleaning
|
|
||||||
const currentDate = new Date()
|
|
||||||
fs.readdir(logDirectory, (err, files) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error reading log directory:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
files.forEach((file) => {
|
|
||||||
const filePath = path.join(logDirectory, file)
|
|
||||||
fs.stat(filePath, (err, stats) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error getting file stats:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check size
|
|
||||||
if (stats.size > size) {
|
|
||||||
fs.unlink(filePath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error deleting log file:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.debug(
|
|
||||||
`Deleted log file due to exceeding size limit: ${filePath}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Check age
|
|
||||||
const creationDate = new Date(stats.ctime)
|
|
||||||
const daysDifference = Math.floor(
|
|
||||||
(currentDate.getTime() - creationDate.getTime()) /
|
|
||||||
(1000 * 3600 * 24)
|
|
||||||
)
|
|
||||||
if (daysDifference > days) {
|
|
||||||
fs.unlink(filePath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error('Error deleting log file:', err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.debug(`Deleted old log file: ${filePath}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Schedule the next execution with doubled delays
|
|
||||||
setTimeout(() => {
|
|
||||||
cleanLogs(maxFileSizeBytes, daysToKeep, delays * 2)
|
|
||||||
}, delays)
|
|
||||||
}
|
|
||||||
24
electron/utils/shortcut.ts
Normal file
24
electron/utils/shortcut.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { getAppConfigurations } from '@janhq/core/node'
|
||||||
|
import { registerShortcut } from './selectedText'
|
||||||
|
import { windowManager } from '../managers/window'
|
||||||
|
// TODO: Retrieve from config later
|
||||||
|
const quickAskHotKey = 'CommandOrControl+J'
|
||||||
|
|
||||||
|
export function registerGlobalShortcuts() {
|
||||||
|
if (!getAppConfigurations().quick_ask) return
|
||||||
|
const ret = registerShortcut(quickAskHotKey, (selectedText: string) => {
|
||||||
|
// Feature Toggle for Quick Ask
|
||||||
|
if (!windowManager.isQuickAskWindowVisible()) {
|
||||||
|
windowManager.showQuickAskWindow()
|
||||||
|
windowManager.sendQuickAskSelectedText(selectedText)
|
||||||
|
} else {
|
||||||
|
windowManager.hideQuickAskWindow()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
console.error('Global shortcut registration failed')
|
||||||
|
} else {
|
||||||
|
console.log('Global shortcut registered successfully')
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan Assistant plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/assistant-extension",
|
"name": "@janhq/assistant-extension",
|
||||||
|
"productName": "Jan Assistant Extension",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
|
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@ -7,12 +7,10 @@ import replace from '@rollup/plugin-replace'
|
|||||||
|
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
|
||||||
const pkg = require('./package.json')
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
input: `src/index.ts`,
|
input: `src/index.ts`,
|
||||||
output: [{ file: pkg.main, format: 'es', sourcemap: true }],
|
output: [{ file: packageJson.main, format: 'es', sourcemap: true }],
|
||||||
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
||||||
external: [],
|
external: [],
|
||||||
watch: {
|
watch: {
|
||||||
@ -20,8 +18,8 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({
|
replace({
|
||||||
|
preventAssignment: true,
|
||||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||||
EXTENSION_NAME: JSON.stringify(packageJson.name),
|
|
||||||
VERSION: JSON.stringify(packageJson.version),
|
VERSION: JSON.stringify(packageJson.version),
|
||||||
}),
|
}),
|
||||||
// Allow json resolution
|
// Allow json resolution
|
||||||
@ -36,7 +34,7 @@ export default [
|
|||||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||||
resolve({
|
resolve({
|
||||||
extensions: ['.js', '.ts', '.svelte'],
|
extensions: ['.js', '.ts', '.svelte'],
|
||||||
browser: true
|
browser: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Resolve source maps to the original source
|
// Resolve source maps to the original source
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
declare const NODE: string
|
declare const NODE: string
|
||||||
declare const EXTENSION_NAME: string
|
|
||||||
declare const VERSION: string
|
declare const VERSION: string
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export default class JanAssistantExtension extends AssistantExtension {
|
|||||||
JanAssistantExtension._homeDir
|
JanAssistantExtension._homeDir
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
localStorage.getItem(`${EXTENSION_NAME}-version`) !== VERSION ||
|
localStorage.getItem(`${this.name}-version`) !== VERSION ||
|
||||||
!assistantDirExist
|
!assistantDirExist
|
||||||
) {
|
) {
|
||||||
if (!assistantDirExist) await fs.mkdir(JanAssistantExtension._homeDir)
|
if (!assistantDirExist) await fs.mkdir(JanAssistantExtension._homeDir)
|
||||||
@ -29,7 +29,7 @@ export default class JanAssistantExtension extends AssistantExtension {
|
|||||||
// Write assistant metadata
|
// Write assistant metadata
|
||||||
await this.createJanAssistant()
|
await this.createJanAssistant()
|
||||||
// Finished migration
|
// Finished migration
|
||||||
localStorage.setItem(`${EXTENSION_NAME}-version`, VERSION)
|
localStorage.setItem(`${this.name}-version`, VERSION)
|
||||||
// Update the assistant list
|
// Update the assistant list
|
||||||
events.emit(AssistantEvent.OnAssistantsUpdate, {})
|
events.emit(AssistantEvent.OnAssistantsUpdate, {})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,36 @@
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { getJanDataFolderPath } from '@janhq/core/node'
|
import { SettingComponentProps, getJanDataFolderPath } from '@janhq/core/node'
|
||||||
|
|
||||||
// Sec: Do not send engine settings over requests
|
// Sec: Do not send engine settings over requests
|
||||||
// Read it manually instead
|
// Read it manually instead
|
||||||
export const readEmbeddingEngine = (engineName: string) => {
|
export const readEmbeddingEngine = (engineName: string) => {
|
||||||
const engineSettings = fs.readFileSync(
|
if (engineName !== 'openai' && engineName !== 'groq') {
|
||||||
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
const engineSettings = fs.readFileSync(
|
||||||
'utf-8'
|
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
||||||
)
|
'utf-8'
|
||||||
return JSON.parse(engineSettings)
|
)
|
||||||
|
return JSON.parse(engineSettings)
|
||||||
|
} else {
|
||||||
|
const settingDirectoryPath = path.join(
|
||||||
|
getJanDataFolderPath(),
|
||||||
|
'settings',
|
||||||
|
engineName === 'openai'
|
||||||
|
? 'inference-openai-extension'
|
||||||
|
: 'inference-groq-extension',
|
||||||
|
'settings.json'
|
||||||
|
)
|
||||||
|
|
||||||
|
const content = fs.readFileSync(settingDirectoryPath, 'utf-8')
|
||||||
|
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||||
|
const apiKeyId = engineName === 'openai' ? 'openai-api-key' : 'groq-api-key'
|
||||||
|
const keySetting = settings.find((setting) => setting.key === apiKeyId)
|
||||||
|
|
||||||
|
let apiKey = keySetting?.controllerProps.value
|
||||||
|
if (typeof apiKey !== 'string') apiKey = ''
|
||||||
|
|
||||||
|
return {
|
||||||
|
api_key: apiKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/conversational-extension",
|
"name": "@janhq/conversational-extension",
|
||||||
|
"productName": "Conversational Extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This extension enables conversations and state persistence via your filesystem",
|
"description": "This extension enables conversations and state persistence via your filesystem",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
# Create a Jan Plugin using Typescript
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
## Create Your Own Plugin
|
## Create Your Own Extension
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -14,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -39,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/huggingface-extension",
|
"name": "@janhq/huggingface-extension",
|
||||||
|
"productName": "HuggingFace Extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Hugging Face extension for converting HF models to GGUF",
|
"description": "Hugging Face extension for converting HF models to GGUF",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default [
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
replace({
|
replace({
|
||||||
EXTENSION_NAME: JSON.stringify(packageJson.name),
|
preventAssignment: true,
|
||||||
NODE_MODULE_PATH: JSON.stringify(
|
NODE_MODULE_PATH: JSON.stringify(
|
||||||
`${packageJson.name}/${packageJson.node}`
|
`${packageJson.name}/${packageJson.node}`
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
declare const EXTENSION_NAME: string
|
|
||||||
declare const NODE_MODULE_PATH: string
|
declare const NODE_MODULE_PATH: string
|
||||||
|
|||||||
@ -338,7 +338,7 @@ export default class JanHuggingFaceExtension extends HuggingFaceExtension {
|
|||||||
|
|
||||||
const metadata: Model = {
|
const metadata: Model = {
|
||||||
object: 'model',
|
object: 'model',
|
||||||
version: 1,
|
version: '1.0',
|
||||||
format: 'gguf',
|
format: 'gguf',
|
||||||
sources: [
|
sources: [
|
||||||
{
|
{
|
||||||
|
|||||||
@ -32,13 +32,9 @@ export const getQuantizeExecutable = (): string => {
|
|||||||
binaryName = 'quantize.exe'
|
binaryName = 'quantize.exe'
|
||||||
} else if (process.platform === 'darwin') {
|
} else if (process.platform === 'darwin') {
|
||||||
/**
|
/**
|
||||||
* For MacOS: mac-arm64 (Silicon), mac-x64 (InteL)
|
* For MacOS: mac-universal both Silicon and InteL
|
||||||
*/
|
*/
|
||||||
if (process.arch === 'arm64') {
|
binaryFolder = pjoin(binaryFolder, 'mac-universal')
|
||||||
binaryFolder = pjoin(binaryFolder, 'mac-arm64')
|
|
||||||
} else {
|
|
||||||
binaryFolder = pjoin(binaryFolder, 'mac-x64')
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
binaryFolder = pjoin(binaryFolder, 'linux-cpu')
|
binaryFolder = pjoin(binaryFolder, 'linux-cpu')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan inference plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,36 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from "@janhq/core";
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/inference-groq-extension",
|
"name": "@janhq/inference-groq-extension",
|
||||||
|
"productName": "Groq Inference Engine Extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This extension enables fast Groq chat completion API calls",
|
"description": "This extension enables fast Groq chat completion API calls",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
58
extensions/inference-groq-extension/resources/models.json
Normal file
58
extensions/inference-groq-extension/resources/models.json
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://groq.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "llama2-70b-4096",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Groq Llama 2 70b",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Groq Llama 2 70b with supercharged speed!",
|
||||||
|
"format": "api",
|
||||||
|
"settings": {
|
||||||
|
"text_model": false
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 1,
|
||||||
|
"stop": null,
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Meta",
|
||||||
|
"tags": ["General", "Big Context Length"]
|
||||||
|
},
|
||||||
|
"engine": "groq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://groq.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "mixtral-8x7b-32768",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Groq Mixtral 8x7b Instruct",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Groq Mixtral 8x7b Instruct is Mixtral with supercharged speed!",
|
||||||
|
"format": "api",
|
||||||
|
"settings": {
|
||||||
|
"text_model": false
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 1,
|
||||||
|
"stop": null,
|
||||||
|
"stream": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral",
|
||||||
|
"tags": ["General", "Big Context Length"]
|
||||||
|
},
|
||||||
|
"engine": "groq"
|
||||||
|
}
|
||||||
|
]
|
||||||
23
extensions/inference-groq-extension/resources/settings.json
Normal file
23
extensions/inference-groq-extension/resources/settings.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "chat-completions-endpoint",
|
||||||
|
"title": "Chat Completions Endpoint",
|
||||||
|
"description": "The endpoint to use for chat completions. See the [Groq documentation](https://console.groq.com/docs/openai) for more information.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "https://api.groq.com/openai/v1/chat/completions",
|
||||||
|
"value": "https://api.groq.com/openai/v1/chat/completions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "groq-api-key",
|
||||||
|
"title": "API Key",
|
||||||
|
"description": "The Groq API uses API keys for authentication. Visit your [API Keys](https://console.groq.com/keys) page to retrieve the API key you'll use in your requests.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "gsk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"value": "",
|
||||||
|
"type": "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -6,78 +6,62 @@
|
|||||||
* @module inference-groq-extension/src/index
|
* @module inference-groq-extension/src/index
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { RemoteOAIEngine, SettingComponentProps } from '@janhq/core'
|
||||||
events,
|
|
||||||
fs,
|
|
||||||
AppConfigurationEventName,
|
|
||||||
joinPath,
|
|
||||||
RemoteOAIEngine,
|
|
||||||
} from '@janhq/core'
|
|
||||||
import { join } from 'path'
|
|
||||||
|
|
||||||
declare const COMPLETION_URL: string
|
declare const SETTINGS: Array<any>
|
||||||
|
declare const MODELS: Array<any>
|
||||||
|
|
||||||
|
enum Settings {
|
||||||
|
apiKey = 'groq-api-key',
|
||||||
|
chatCompletionsEndPoint = 'chat-completions-endpoint',
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
*/
|
*/
|
||||||
export default class JanInferenceGroqExtension extends RemoteOAIEngine {
|
export default class JanInferenceGroqExtension extends RemoteOAIEngine {
|
||||||
private readonly _engineDir = 'file://engines'
|
inferenceUrl: string = ''
|
||||||
private readonly _engineMetadataFileName = 'groq.json'
|
|
||||||
|
|
||||||
inferenceUrl: string = COMPLETION_URL
|
|
||||||
provider = 'groq'
|
provider = 'groq'
|
||||||
apiKey = ''
|
|
||||||
|
|
||||||
private _engineSettings = {
|
override async onLoad(): Promise<void> {
|
||||||
full_url: COMPLETION_URL,
|
|
||||||
api_key: 'gsk-<your key here>',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to events emitted by the @janhq/core package.
|
|
||||||
*/
|
|
||||||
async onLoad() {
|
|
||||||
super.onLoad()
|
super.onLoad()
|
||||||
|
|
||||||
if (!(await fs.existsSync(this._engineDir))) {
|
// Register Settings
|
||||||
await fs.mkdir(this._engineDir)
|
this.registerSettings(SETTINGS)
|
||||||
}
|
this.registerModels(MODELS)
|
||||||
|
|
||||||
this.writeDefaultEngineSettings()
|
// Retrieve API Key Setting
|
||||||
|
this.apiKey = await this.getSetting<string>(Settings.apiKey, '')
|
||||||
const settingsFilePath = await joinPath([
|
this.inferenceUrl = await this.getSetting<string>(
|
||||||
this._engineDir,
|
Settings.chatCompletionsEndPoint,
|
||||||
this._engineMetadataFileName,
|
''
|
||||||
])
|
|
||||||
|
|
||||||
// Events subscription
|
|
||||||
events.on(
|
|
||||||
AppConfigurationEventName.OnConfigurationUpdate,
|
|
||||||
(settingsKey: string) => {
|
|
||||||
// Update settings on changes
|
|
||||||
if (settingsKey === settingsFilePath) this.writeDefaultEngineSettings()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (this.inferenceUrl.length === 0) {
|
||||||
|
SETTINGS.forEach((setting) => {
|
||||||
|
if (setting.key === Settings.chatCompletionsEndPoint) {
|
||||||
|
this.inferenceUrl = setting.controllerProps.value as string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeDefaultEngineSettings() {
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
try {
|
if (key === Settings.apiKey) {
|
||||||
const engineFile = join(this._engineDir, this._engineMetadataFileName)
|
this.apiKey = value as string
|
||||||
if (await fs.existsSync(engineFile)) {
|
} else if (key === Settings.chatCompletionsEndPoint) {
|
||||||
const engine = await fs.readFileSync(engineFile, 'utf-8')
|
if (typeof value !== 'string') return
|
||||||
this._engineSettings =
|
|
||||||
typeof engine === 'object' ? engine : JSON.parse(engine)
|
if (value.trim().length === 0) {
|
||||||
this.inferenceUrl = this._engineSettings.full_url
|
SETTINGS.forEach((setting) => {
|
||||||
this.apiKey = this._engineSettings.api_key
|
if (setting.key === Settings.chatCompletionsEndPoint) {
|
||||||
|
this.inferenceUrl = setting.controllerProps.value as string
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
await fs.writeFileSync(
|
this.inferenceUrl = value
|
||||||
engineFile,
|
|
||||||
JSON.stringify(this._engineSettings, null, 2)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
|
const modelsJson = require('./resources/models.json')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
experiments: { outputModule: true },
|
experiments: { outputModule: true },
|
||||||
@ -17,8 +19,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
|
MODELS: JSON.stringify(modelsJson),
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||||
COMPLETION_URL: JSON.stringify('https://api.groq.com/openai/v1/chat/completions'),
|
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
output: {
|
output: {
|
||||||
|
|||||||
79
extensions/inference-mistral-extension/README.md
Normal file
79
extensions/inference-mistral-extension/README.md
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# Mistral Engine Extension
|
||||||
|
|
||||||
|
Created using Jan extension example
|
||||||
|
|
||||||
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
|
## Create Your Own Extension
|
||||||
|
|
||||||
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
|
1. Click the Use this template button at the top of the repository
|
||||||
|
2. Select Create a new repository
|
||||||
|
3. Select an owner and name for your new repository
|
||||||
|
4. Click Create repository
|
||||||
|
5. Clone your new repository
|
||||||
|
|
||||||
|
## Initial Setup
|
||||||
|
|
||||||
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
>
|
||||||
|
> You'll need to have a reasonably modern version of
|
||||||
|
> [Node.js](https://nodejs.org) handy. If you are using a version manager like
|
||||||
|
> [`nodenv`](https://github.com/nodenv/nodenv) or
|
||||||
|
> [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the
|
||||||
|
> root of your repository to install the version specified in
|
||||||
|
> [`package.json`](./package.json). Otherwise, 20.x or later should work!
|
||||||
|
|
||||||
|
1. :hammer_and_wrench: Install the dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
1. :building_construction: Package the TypeScript for distribution
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
|
## Update the Extension Metadata
|
||||||
|
|
||||||
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
|
## Update the Extension Code
|
||||||
|
|
||||||
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
|
contents of this directory with your own code.
|
||||||
|
|
||||||
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
|
function onStart(): Promise<any> {
|
||||||
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information about the Jan Extension Core module, see the
|
||||||
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
43
extensions/inference-mistral-extension/package.json
Normal file
43
extensions/inference-mistral-extension/package.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "@janhq/inference-mistral-extension",
|
||||||
|
"productName": "Mistral AI Inference Engine Extension",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "This extension enables Mistral chat completion API calls",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"module": "dist/module.js",
|
||||||
|
"engine": "mistral",
|
||||||
|
"author": "Jan <service@jan.ai>",
|
||||||
|
"license": "AGPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||||
|
"build:publish": "rimraf *.tgz --glob && yarn build && npm pack && cpx *.tgz ../../pre-install"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.js",
|
||||||
|
"./main": "./dist/module.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cpx": "^1.5.0",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"webpack": "^5.88.2",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"ts-loader": "^9.5.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@janhq/core": "file:../../core",
|
||||||
|
"fetch-retry": "^5.0.6",
|
||||||
|
"path-browserify": "^1.0.1",
|
||||||
|
"ulidx": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/*",
|
||||||
|
"package.json",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"bundleDependencies": [
|
||||||
|
"fetch-retry"
|
||||||
|
]
|
||||||
|
}
|
||||||
85
extensions/inference-mistral-extension/resources/models.json
Normal file
85
extensions/inference-mistral-extension/resources/models.json
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.mistral.ai/api/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "mistral-small-latest",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Mistral Small",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Mistral Small is the ideal choice for simpe tasks that one can do in builk - like Classification, Customer Support, or Text Generation. It offers excellent performance at an affordable price point.",
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"temperature": 0.7
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral",
|
||||||
|
"tags": [
|
||||||
|
"Classification",
|
||||||
|
"Customer Support",
|
||||||
|
"Text Generation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engine": "mistral"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.mistral.ai/api/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "mistral-medium-latest",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Mistral Medium",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Mistral Medium is the ideal for intermediate tasks that require moderate reasoning - like Data extraction, Summarizing a Document, Writing a Job Description, or Writing Product Descriptions. Mistral Medium strikes a balance between performance and capability, making it suitable for a wide range of tasks that only require language transformaion",
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"temperature": 0.7
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral",
|
||||||
|
"tags": [
|
||||||
|
"Data extraction",
|
||||||
|
"Summarizing a Document",
|
||||||
|
"Writing a Job Description",
|
||||||
|
"Writing Product Descriptions"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engine": "mistral"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"url": "https://docs.mistral.ai/api/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "mistral-large-latest",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Mistral Large",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Mistral Large is ideal for complex tasks that require large reasoning capabilities or are highly specialized - like Synthetic Text Generation, Code Generation, RAG, or Agents.",
|
||||||
|
"format": "api",
|
||||||
|
"settings": {},
|
||||||
|
"parameters": {
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"temperature": 0.7
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "Mistral",
|
||||||
|
"tags": [
|
||||||
|
"Text Generation",
|
||||||
|
"Code Generation",
|
||||||
|
"RAG",
|
||||||
|
"Agents"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engine": "mistral"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "chat-completions-endpoint",
|
||||||
|
"title": "Chat Completions Endpoint",
|
||||||
|
"description": "The endpoint to use for chat completions. See the [Mistral API documentation](https://docs.mistral.ai/api/#operation/createChatCompletion) for more information.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "https://api.mistral.ai/v1/chat/completions",
|
||||||
|
"value": "https://api.mistral.ai/v1/chat/completions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "mistral-api-key",
|
||||||
|
"title": "API Key",
|
||||||
|
"description": "The Mistral API uses API keys for authentication. Visit your [API Keys](https://console.mistral.ai/api-keys/) page to retrieve the API key you'll use in your requests.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||||
|
"value": "",
|
||||||
|
"type": "password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
66
extensions/inference-mistral-extension/src/index.ts
Normal file
66
extensions/inference-mistral-extension/src/index.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* @file This file exports a class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
|
* @version 1.0.0
|
||||||
|
* @module inference-mistral-extension/src/index
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { RemoteOAIEngine } from '@janhq/core'
|
||||||
|
|
||||||
|
declare const SETTINGS: Array<any>
|
||||||
|
declare const MODELS: Array<any>
|
||||||
|
|
||||||
|
enum Settings {
|
||||||
|
apiKey = 'mistral-api-key',
|
||||||
|
chatCompletionsEndPoint = 'chat-completions-endpoint',
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* A class that implements the InferenceExtension interface from the @janhq/core package.
|
||||||
|
* The class provides methods for initializing and stopping a model, and for making inference requests.
|
||||||
|
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
|
||||||
|
*/
|
||||||
|
export default class JanInferenceMistralExtension extends RemoteOAIEngine {
|
||||||
|
inferenceUrl: string = ''
|
||||||
|
provider: string = 'mistral'
|
||||||
|
|
||||||
|
override async onLoad(): Promise<void> {
|
||||||
|
super.onLoad()
|
||||||
|
|
||||||
|
// Register Settings
|
||||||
|
this.registerSettings(SETTINGS)
|
||||||
|
this.registerModels(MODELS)
|
||||||
|
|
||||||
|
this.apiKey = await this.getSetting<string>(Settings.apiKey, '')
|
||||||
|
this.inferenceUrl = await this.getSetting<string>(
|
||||||
|
Settings.chatCompletionsEndPoint,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this.inferenceUrl.length === 0) {
|
||||||
|
SETTINGS.forEach((setting) => {
|
||||||
|
if (setting.key === Settings.chatCompletionsEndPoint) {
|
||||||
|
this.inferenceUrl = setting.controllerProps.value as string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSettingUpdate<T>(key: string, value: T): void {
|
||||||
|
if (key === Settings.apiKey) {
|
||||||
|
this.apiKey = value as string
|
||||||
|
} else if (key === Settings.chatCompletionsEndPoint) {
|
||||||
|
if (typeof value !== 'string') return
|
||||||
|
|
||||||
|
if (value.trim().length === 0) {
|
||||||
|
SETTINGS.forEach((setting) => {
|
||||||
|
if (setting.key === Settings.chatCompletionsEndPoint) {
|
||||||
|
this.inferenceUrl = setting.controllerProps.value as string
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.inferenceUrl = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
extensions/inference-mistral-extension/tsconfig.json
Normal file
14
extensions/inference-mistral-extension/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2016",
|
||||||
|
"module": "ES6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"rootDir": "./src"
|
||||||
|
},
|
||||||
|
"include": ["./src"]
|
||||||
|
}
|
||||||
42
extensions/inference-mistral-extension/webpack.config.js
Normal file
42
extensions/inference-mistral-extension/webpack.config.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const webpack = require('webpack')
|
||||||
|
const packageJson = require('./package.json')
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
|
const modelsJson = require('./resources/models.json')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
experiments: { outputModule: true },
|
||||||
|
entry: './src/index.ts', // Adjust the entry point to match your project's main file
|
||||||
|
mode: 'production',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
|
ENGINE: JSON.stringify(packageJson.engine),
|
||||||
|
MODELS: JSON.stringify(modelsJson),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
filename: 'index.js', // Adjust the output file name as needed
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
library: { type: 'module' }, // Specify ESM output format
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
fallback: {
|
||||||
|
path: require.resolve('path-browserify'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
|
// Add loaders and other configuration as needed for your project
|
||||||
|
}
|
||||||
@ -1,14 +1,10 @@
|
|||||||
# Jan inference plugin
|
# Create a Jan Extension using Typescript
|
||||||
|
|
||||||
Created using Jan app example
|
Use this template to bootstrap the creation of a TypeScript Jan extension. 🚀
|
||||||
|
|
||||||
# Create a Jan Plugin using Typescript
|
## Create Your Own Extension
|
||||||
|
|
||||||
Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀
|
To create your own extension, you can use this repository as a template! Just follow the below instructions:
|
||||||
|
|
||||||
## Create Your Own Plugin
|
|
||||||
|
|
||||||
To create your own plugin, you can use this repository as a template! Just follow the below instructions:
|
|
||||||
|
|
||||||
1. Click the Use this template button at the top of the repository
|
1. Click the Use this template button at the top of the repository
|
||||||
2. Select Create a new repository
|
2. Select Create a new repository
|
||||||
@ -18,7 +14,7 @@ To create your own plugin, you can use this repository as a template! Just follo
|
|||||||
|
|
||||||
## Initial Setup
|
## Initial Setup
|
||||||
|
|
||||||
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin.
|
After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your extension.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
>
|
>
|
||||||
@ -43,35 +39,37 @@ After you've cloned the repository to your local machine or codespace, you'll ne
|
|||||||
|
|
||||||
1. :white_check_mark: Check your artifact
|
1. :white_check_mark: Check your artifact
|
||||||
|
|
||||||
There will be a tgz file in your plugin directory now
|
There will be a tgz file in your extension directory now
|
||||||
|
|
||||||
## Update the Plugin Metadata
|
## Update the Extension Metadata
|
||||||
|
|
||||||
The [`package.json`](package.json) file defines metadata about your plugin, such as
|
The [`package.json`](package.json) file defines metadata about your extension, such as
|
||||||
plugin name, main entry, description and version.
|
extension name, main entry, description and version.
|
||||||
|
|
||||||
When you copy this repository, update `package.json` with the name, description for your plugin.
|
When you copy this repository, update `package.json` with the name, description for your extension.
|
||||||
|
|
||||||
## Update the Plugin Code
|
## Update the Extension Code
|
||||||
|
|
||||||
The [`src/`](./src/) directory is the heart of your plugin! This contains the
|
The [`src/`](./src/) directory is the heart of your extension! This contains the
|
||||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
source code that will be run when your extension functions are invoked. You can replace the
|
||||||
contents of this directory with your own code.
|
contents of this directory with your own code.
|
||||||
|
|
||||||
There are a few things to keep in mind when writing your plugin code:
|
There are a few things to keep in mind when writing your extension code:
|
||||||
|
|
||||||
- Most Jan Plugin Extension functions are processed asynchronously.
|
- Most Jan Extension functions are processed asynchronously.
|
||||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { core } from '@janhq/core'
|
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||||
|
|
||||||
function onStart(): Promise<any> {
|
function onStart(): Promise<any> {
|
||||||
return core.invokePluginFunc(MODULE_PATH, 'run', 0)
|
return events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about the Jan Plugin Core module, see the
|
For more information about the Jan Extension Core module, see the
|
||||||
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
[documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||||
|
|
||||||
So, what are you waiting for? Go ahead and start customizing your plugin!
|
So, what are you waiting for? Go ahead and start customizing your extension!
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0.3.14
|
0.3.21
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
@echo off
|
@echo off
|
||||||
set /p NITRO_VERSION=<./bin/version.txt
|
set /p NITRO_VERSION=<./bin/version.txt
|
||||||
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/win-vulkan
|
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-avx2-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-avx2-cuda-11-7.tar.gz -e --strip 1 -o ./bin/win-cuda-11-7 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-avx2.tar.gz -e --strip 1 -o ./bin/win-cpu && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/win-vulkan
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/inference-nitro-extension",
|
"name": "@janhq/inference-nitro-extension",
|
||||||
|
"productName": "Nitro Inference Engine Extension",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See nitro.jan.ai",
|
"description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See https://nitro.jan.ai.\nUse this setting if you encounter errors related to **CUDA toolkit** during application execution.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"node": "dist/node/index.cjs.js",
|
"node": "dist/node/index.cjs.js",
|
||||||
"author": "Jan <service@jan.ai>",
|
"author": "Jan <service@jan.ai>",
|
||||||
@ -9,8 +10,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||||
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/linux-vulkan && chmod +x ./bin/linux-vulkan/nitro",
|
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-avx2.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-7.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-7 && chmod +x ./bin/linux-cuda-11-7/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-vulkan.tar.gz -e --strip 1 -o ./bin/linux-vulkan && chmod +x ./bin/linux-vulkan/nitro",
|
||||||
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-universal.tar.gz -o ./bin/ && mkdir -p ./bin/mac-universal && tar -zxvf ./bin/nitro-${NITRO_VERSION}-mac-universal.tar.gz --strip-components=1 -C ./bin/mac-universal && rm -rf ./bin/nitro-${NITRO_VERSION}-mac-universal.tar.gz && chmod +x ./bin/mac-universal/nitro",
|
||||||
"downloadnitro:win32": "download.bat",
|
"downloadnitro:win32": "download.bat",
|
||||||
"downloadnitro": "run-script-os",
|
"downloadnitro": "run-script-os",
|
||||||
"build:publish:darwin": "rimraf *.tgz --glob && yarn build && npm run downloadnitro && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install",
|
"build:publish:darwin": "rimraf *.tgz --glob && yarn build && npm run downloadnitro && ../../.github/scripts/auto-sign.sh && cpx \"bin/**\" \"dist/bin\" && npm pack && cpx *.tgz ../../pre-install",
|
||||||
@ -29,6 +30,7 @@
|
|||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-replace": "^5.0.5",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
|
"@types/decompress": "^4.2.7",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^20.11.4",
|
"@types/node": "^20.11.4",
|
||||||
"@types/os-utils": "^0.0.4",
|
"@types/os-utils": "^0.0.4",
|
||||||
@ -47,10 +49,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@janhq/core": "file:../../core",
|
"@janhq/core": "file:../../core",
|
||||||
|
"decompress": "^4.2.1",
|
||||||
"fetch-retry": "^5.0.6",
|
"fetch-retry": "^5.0.6",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"tcp-port-used": "^1.0.2",
|
"tcp-port-used": "^1.0.2",
|
||||||
|
"terminate": "^2.6.1",
|
||||||
"ulidx": "^2.3.0"
|
"ulidx": "^2.3.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -64,6 +68,7 @@
|
|||||||
"bundleDependencies": [
|
"bundleDependencies": [
|
||||||
"tcp-port-used",
|
"tcp-port-used",
|
||||||
"fetch-retry",
|
"fetch-retry",
|
||||||
"@janhq/core"
|
"@janhq/core",
|
||||||
|
"decompress"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "test",
|
||||||
|
"title": "Test",
|
||||||
|
"description": "Test",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"placeholder": "Test",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "embedding",
|
||||||
|
"title": "Embedding",
|
||||||
|
"description": "Whether to enable embedding.",
|
||||||
|
"controllerType": "checkbox",
|
||||||
|
"controllerProps": {
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ctx_len",
|
||||||
|
"title": "Context Length",
|
||||||
|
"description": "The context length for model operations varies; the maximum depends on the specific model used.",
|
||||||
|
"controllerType": "slider",
|
||||||
|
"controllerProps": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 4096,
|
||||||
|
"step": 128,
|
||||||
|
"value": 4096
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
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