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:
|
||||
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]
|
||||
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
|
||||
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
|
||||
uses: actions/setup-node@v3
|
||||
@ -62,29 +102,24 @@ jobs:
|
||||
|
||||
- name: Linter and test
|
||||
run: |
|
||||
npm config set registry https://registry.npmjs.org --global
|
||||
yarn config set registry https://registry.npmjs.org --global
|
||||
make test
|
||||
env:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
|
||||
test-on-windows:
|
||||
if: github.event_name == 'push'
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
||||
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
||||
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
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing node
|
||||
uses: actions/setup-node@v3
|
||||
@ -97,26 +132,79 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: |
|
||||
make clean
|
||||
|
||||
- name: Get Commit Message for push event
|
||||
if : github.event_name == 'push'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
||||
|
||||
- name: "Config report portal"
|
||||
shell: bash
|
||||
run: |
|
||||
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||
|
||||
- name: Linter and test
|
||||
shell: powershell
|
||||
run: |
|
||||
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:
|
||||
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
|
||||
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
|
||||
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
|
||||
uses: actions/setup-node@v1
|
||||
@ -133,20 +221,65 @@ jobs:
|
||||
- name: Linter and test
|
||||
shell: powershell
|
||||
run: |
|
||||
npm config set registry https://registry.npmjs.org --global
|
||||
yarn config set registry https://registry.npmjs.org --global
|
||||
make test
|
||||
|
||||
|
||||
test-on-ubuntu:
|
||||
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:
|
||||
- name: "Cleanup build folder"
|
||||
run: |
|
||||
ls -la ./
|
||||
rm -rf ./* || true
|
||||
rm -rf ./.??* || true
|
||||
ls -la ./
|
||||
rm -rf ~/.config/jan
|
||||
- name: Getting the repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Installing node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: "Cleanup cache"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
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
|
||||
uses: actions/setup-node@v3
|
||||
@ -162,4 +295,6 @@ jobs:
|
||||
run: |
|
||||
export DISPLAY=$(w -h | awk 'NR==1 {print $2}')
|
||||
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:
|
||||
args: |
|
||||
Jan App ${{ inputs.build_reason }} build artifact version {{ VERSION }}:
|
||||
- Windows: https://delta.jan.ai/latest/jan-win-x64-{{ VERSION }}.exe
|
||||
- macOS Intel: https://delta.jan.ai/latest/jan-mac-x64-{{ VERSION }}.dmg
|
||||
- macOS Apple Silicon: https://delta.jan.ai/latest/jan-mac-arm64-{{ VERSION }}.dmg
|
||||
- Linux Deb: https://delta.jan.ai/latest/jan-linux-amd64-{{ VERSION }}.deb
|
||||
- Linux AppImage: https://delta.jan.ai/latest/jan-linux-x86_64-{{ VERSION }}.AppImage
|
||||
- Windows: https://app.jan.ai/download/nightly/win-x64
|
||||
- macOS Intel: https://app.jan.ai/download/nightly/mac-x64
|
||||
- macOS Apple Silicon: https://app.jan.ai/download/nightly/mac-arm64
|
||||
- Linux Deb: https://app.jan.ai/download/nightly/linux-amd64-deb
|
||||
- Linux AppImage: https://app.jan.ai/download/nightly/linux-amd64-appimage
|
||||
- Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
|
||||
- name: Update README.md with artifact URL
|
||||
run: |
|
||||
sed -i "s|<a href='https://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 }}
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
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
|
||||
.idea
|
||||
.env
|
||||
.idea
|
||||
|
||||
# Jan inference
|
||||
error.log
|
||||
@ -35,4 +37,4 @@ extensions/*-extension/bin/vulkaninfo
|
||||
|
||||
|
||||
# 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 --from=builder /app/uikit ./uikit/
|
||||
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/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 --from=builder /app/uikit ./uikit/
|
||||
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/web install
|
||||
|
||||
82
Makefile
82
Makefile
@ -1,5 +1,11 @@
|
||||
# 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
|
||||
all:
|
||||
@echo "Specify a target to run"
|
||||
@ -37,6 +43,64 @@ dev: check-file-counts
|
||||
lint: check-file-counts
|
||||
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
|
||||
test: lint
|
||||
yarn build:test
|
||||
@ -53,19 +117,24 @@ build: check-file-counts
|
||||
|
||||
clean:
|
||||
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 package-lock.json -Recurse -File | Remove-Item -Recurse -Force"
|
||||
powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.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 }"
|
||||
-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 yarn.lock -Recurse -File | Remove-Item -Recurse -Force"
|
||||
-powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz"
|
||||
-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)
|
||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||
find . -name "build" -type d -exec rm -rf '{}' +
|
||||
find . -name "out" -type d -exec rm -rf '{}' +
|
||||
find . -name ".turbo" -type d -exec rm -rf '{}' +
|
||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||
find . -name "yarn.lock" -type f -exec rm -rf '{}' +
|
||||
rm -rf ./pre-install/*.tgz
|
||||
rm -rf ./extensions/*/*.tgz
|
||||
rm -rf ./electron/pre-install/*.tgz
|
||||
rm -rf "~/jan/extensions"
|
||||
rm -rf "~/.cache/jan*"
|
||||
@ -75,8 +144,11 @@ else
|
||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||
find . -name "build" -type d -exec rm -rf '{}' +
|
||||
find . -name "out" -type d -exec rm -rf '{}' +
|
||||
find . -name ".turbo" -type d -exec rm -rf '{}' +
|
||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||
find . -name "yarn.lock" -type f -exec rm -rf '{}' +
|
||||
rm -rf ./pre-install/*.tgz
|
||||
rm -rf ./extensions/*/*.tgz
|
||||
rm -rf ./electron/pre-install/*.tgz
|
||||
rm -rf ~/jan/extensions
|
||||
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">
|
||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-win-x64-0.4.9.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/latest/win-x64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-x64-0.4.9.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<a href='https://app.jan.ai/download/latest/mac-x64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-arm64-0.4.9.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<a href='https://app.jan.ai/download/latest/mac-arm64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-linux-amd64-0.4.9.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/latest/linux-amd64-deb'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<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'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/latest/linux-amd64-appimage'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
</td>
|
||||
@ -76,32 +76,32 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.9-345.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/nightly/win-x64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.9-345.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<a href='https://app.jan.ai/download/nightly/mac-x64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.9-345.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<a href='https://app.jan.ai/download/nightly/mac-arm64'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.9-345.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/nightly/linux-amd64-deb'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.9-345.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<a href='https://app.jan.ai/download/nightly/linux-amd64-appimage'>
|
||||
<img src='https://github.com/janhq/docs/blob/main/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
</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.
|
||||
|
||||
- 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 |
|
||||
| ---------------------- | -------------------------------------------- |
|
||||
@ -336,6 +337,15 @@ Jan builds on top of other open-source projects:
|
||||
- For business inquiries: email hello@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
|
||||
|
||||
Jan is free and open source, under the AGPLv3 license.
|
||||
|
||||
@ -30,6 +30,7 @@ export default [
|
||||
// which external modules to include in the bundle
|
||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||
replace({
|
||||
'preventAssignment': true,
|
||||
'node:crypto': 'crypto',
|
||||
'delimiters': ['"', '"'],
|
||||
}),
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import { SettingComponentProps } from '../types'
|
||||
import { getJanDataFolderPath, joinPath } from './core'
|
||||
import { fs } from './fs'
|
||||
|
||||
export enum ExtensionTypeEnum {
|
||||
Assistant = 'assistant',
|
||||
Conversational = 'conversational',
|
||||
@ -19,9 +23,9 @@ export interface Compatibility {
|
||||
const ALL_INSTALLATION_STATE = [
|
||||
'NotRequired', // not required.
|
||||
'Installed', // require and installed. Good to go.
|
||||
'Updatable', // require and installed but need to be updated.
|
||||
'NotInstalled', // require to be installed.
|
||||
'Corrupted', // require but corrupted. Need to redownload.
|
||||
'NotCompatible', // require but not compatible.
|
||||
] as const
|
||||
|
||||
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.
|
||||
*/
|
||||
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 {ExtensionType} The type of the extension
|
||||
@ -40,11 +81,13 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
type(): ExtensionTypeEnum | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the extension is loaded.
|
||||
* Any initialization logic for the extension should be put here.
|
||||
*/
|
||||
abstract onLoad(): void
|
||||
|
||||
/**
|
||||
* Called when the extension is unloaded.
|
||||
* Any cleanup logic for the extension should be put here.
|
||||
@ -60,11 +103,40 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the extension is updatable.
|
||||
*/
|
||||
updatable(): boolean {
|
||||
return false
|
||||
async registerSettings(settings: SettingComponentProps[]): Promise<void> {
|
||||
if (!this.name) {
|
||||
console.error('Extension name is not defined')
|
||||
return
|
||||
}
|
||||
|
||||
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>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
async install(...args): Promise<void> {
|
||||
async install(): Promise<void> {
|
||||
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
|
||||
*/
|
||||
export abstract class AIEngine extends BaseExtension {
|
||||
private static modelsFolder = 'models'
|
||||
|
||||
// The inference engine
|
||||
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.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)
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -65,40 +101,4 @@ export abstract class AIEngine extends BaseExtension {
|
||||
* Stop inference
|
||||
*/
|
||||
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
|
||||
*/
|
||||
override inference(data: MessageRequest) {
|
||||
override async inference(data: MessageRequest) {
|
||||
if (data.model?.engine?.toString() !== this.provider) return
|
||||
|
||||
const timestamp = Date.now()
|
||||
@ -77,12 +77,14 @@ export abstract class OAIEngine extends AIEngine {
|
||||
...data.model,
|
||||
}
|
||||
|
||||
const header = await this.headers()
|
||||
|
||||
requestInference(
|
||||
this.inferenceUrl,
|
||||
data.messages ?? [],
|
||||
model,
|
||||
this.controller,
|
||||
this.headers()
|
||||
header
|
||||
).subscribe({
|
||||
next: (content: any) => {
|
||||
const messageContent: ThreadContent = {
|
||||
@ -100,7 +102,9 @@ export abstract class OAIEngine extends AIEngine {
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
},
|
||||
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) {
|
||||
message.status = MessageStatus.Stopped
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
@ -131,7 +135,7 @@ export abstract class OAIEngine extends AIEngine {
|
||||
/**
|
||||
* Headers for the inference request
|
||||
*/
|
||||
headers(): HeadersInit {
|
||||
async headers(): Promise<HeadersInit> {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,7 @@ import { OAIEngine } from './OAIEngine'
|
||||
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||
*/
|
||||
export abstract class RemoteOAIEngine extends OAIEngine {
|
||||
// The inference engine
|
||||
abstract apiKey: string
|
||||
apiKey?: string
|
||||
/**
|
||||
* On extension load, subscribe to events.
|
||||
*/
|
||||
@ -17,10 +16,12 @@ export abstract class RemoteOAIEngine extends OAIEngine {
|
||||
/**
|
||||
* Headers for the inference request
|
||||
*/
|
||||
override headers(): HeadersInit {
|
||||
override async headers(): Promise<HeadersInit> {
|
||||
return {
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
'api-key': `${this.apiKey}`,
|
||||
...(this.apiKey && {
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
'api-key': `${this.apiKey}`,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,9 +36,15 @@ export function requestInference(
|
||||
.then(async (response) => {
|
||||
if (!response.ok) {
|
||||
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 = {
|
||||
message: data.error?.message ?? 'Error occurred.',
|
||||
code: data.error?.code ?? ErrorCode.Unknown,
|
||||
code: errorCode,
|
||||
}
|
||||
subscriber.error(error)
|
||||
subscriber.complete()
|
||||
@ -60,14 +66,20 @@ export function requestInference(
|
||||
}
|
||||
const text = decoder.decode(value)
|
||||
const lines = text.trim().split('\n')
|
||||
let cachedLines = ''
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ') && !line.includes('data: [DONE]')) {
|
||||
const data = JSON.parse(line.replace('data: ', ''))
|
||||
content += data.choices[0]?.delta?.content ?? ''
|
||||
if (content.startsWith('assistant: ')) {
|
||||
content = content.replace('assistant: ', '')
|
||||
try {
|
||||
const toParse = cachedLines + line
|
||||
if (!line.includes('data: [DONE]')) {
|
||||
const data = JSON.parse(toParse.replace('data: ', ''))
|
||||
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
|
||||
}
|
||||
|
||||
abstract getGpuSetting(): Promise<GpuSetting>
|
||||
abstract getGpuSetting(): Promise<GpuSetting | undefined>
|
||||
abstract getResourcesInfo(): Promise<any>
|
||||
abstract getCurrentLoad(): Promise<any>
|
||||
abstract getOsInfo(): Promise<OperatingSystemInfo>
|
||||
|
||||
@ -5,19 +5,16 @@ export type Handler = (route: string, args: any) => any
|
||||
|
||||
export class RequestHandler {
|
||||
handler: Handler
|
||||
adataper: RequestAdapter
|
||||
adapter: RequestAdapter
|
||||
|
||||
constructor(handler: Handler, observer?: Function) {
|
||||
this.handler = handler
|
||||
this.adataper = new RequestAdapter(observer)
|
||||
this.adapter = new RequestAdapter(observer)
|
||||
}
|
||||
|
||||
handle() {
|
||||
CoreRoutes.map((route) => {
|
||||
this.handler(route, async (...args: any[]) => {
|
||||
const values = await this.adataper.process(route, ...args)
|
||||
return values
|
||||
})
|
||||
this.handler(route, async (...args: any[]) => this.adapter.process(route, ...args))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { basename, isAbsolute, join, relative } from 'path'
|
||||
|
||||
import { Processor } from './Processor'
|
||||
import { getAppConfigurations as appConfiguration, updateAppConfiguration } from '../../helper'
|
||||
import { log as writeLog, logServer as writeServerLog } from '../../helper/log'
|
||||
import { appResourcePath } from '../../helper/path'
|
||||
import {
|
||||
log as writeLog,
|
||||
appResourcePath,
|
||||
getAppConfigurations as appConfiguration,
|
||||
updateAppConfiguration,
|
||||
} from '../../helper'
|
||||
|
||||
export class App implements Processor {
|
||||
observer?: Function
|
||||
@ -56,13 +59,6 @@ export class App implements Processor {
|
||||
writeLog(args)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message to log file.
|
||||
*/
|
||||
logServer(args: any) {
|
||||
writeServerLog(args)
|
||||
}
|
||||
|
||||
getAppConfigurations() {
|
||||
return appConfiguration()
|
||||
}
|
||||
@ -83,6 +79,7 @@ export class App implements Processor {
|
||||
isVerboseEnabled: args?.isVerboseEnabled,
|
||||
schemaPath: join(await appResourcePath(), 'docs', 'openapi', 'jan.yaml'),
|
||||
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 engineConfiguration = await getEngineConfiguration(requestedModel.engine)
|
||||
|
||||
let apiKey: string | undefined = undefined
|
||||
@ -323,7 +324,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
||||
|
||||
if (engineConfiguration) {
|
||||
apiKey = engineConfiguration.api_key
|
||||
apiUrl = engineConfiguration.full_url
|
||||
apiUrl = engineConfiguration.full_url ?? DEFAULT_CHAT_COMPLETION_URL
|
||||
}
|
||||
|
||||
const headers: Record<string, any> = {
|
||||
@ -334,7 +335,6 @@ export const chatCompletions = async (request: any, reply: any) => {
|
||||
headers['Authorization'] = `Bearer ${apiKey}`
|
||||
headers['api-key'] = apiKey
|
||||
}
|
||||
console.debug(apiUrl)
|
||||
|
||||
if (requestedModel.engine === 'openai' && request.body.stop) {
|
||||
// openai only allows max 4 stop words
|
||||
@ -352,7 +352,7 @@ export const chatCompletions = async (request: any, reply: any) => {
|
||||
reply.code(400).send(response)
|
||||
} else {
|
||||
reply.raw.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Content-Type': request.body.stream === true ? 'text/event-stream' : 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { getJanDataFolderPath, getJanExtensionsPath, getSystemResourceInfo } from '../../../helper'
|
||||
import { logServer } from '../../../helper/log'
|
||||
import {
|
||||
getJanDataFolderPath,
|
||||
getJanExtensionsPath,
|
||||
getSystemResourceInfo,
|
||||
log,
|
||||
} from '../../../helper'
|
||||
import { ChildProcessWithoutNullStreams, spawn } from 'child_process'
|
||||
import { Model, ModelSettingParams, PromptTemplate } from '../../../../types'
|
||||
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
|
||||
if (modelMetadata.settings.prompt_template) {
|
||||
@ -140,7 +144,7 @@ const runNitroAndLoadModel = async (modelId: string, modelSettings: NitroModelSe
|
||||
}
|
||||
|
||||
const spawnNitroProcess = async (): Promise<void> => {
|
||||
logServer(`[NITRO]::Debug: Spawning Nitro subprocess...`)
|
||||
log(`[SERVER]::Debug: Spawning Nitro subprocess...`)
|
||||
|
||||
let binaryFolder = join(
|
||||
getJanExtensionsPath(),
|
||||
@ -155,8 +159,8 @@ const spawnNitroProcess = async (): Promise<void> => {
|
||||
|
||||
const args: string[] = ['1', LOCAL_HOST, NITRO_DEFAULT_PORT.toString()]
|
||||
// Execute the binary
|
||||
logServer(
|
||||
`[NITRO]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||
log(
|
||||
`[SERVER]::Debug: Spawn nitro at path: ${executableOptions.executablePath}, and args: ${args}`
|
||||
)
|
||||
subprocess = spawn(
|
||||
executableOptions.executablePath,
|
||||
@ -172,20 +176,20 @@ const spawnNitroProcess = async (): Promise<void> => {
|
||||
|
||||
// Handle subprocess output
|
||||
subprocess.stdout.on('data', (data: any) => {
|
||||
logServer(`[NITRO]::Debug: ${data}`)
|
||||
log(`[SERVER]::Debug: ${data}`)
|
||||
})
|
||||
|
||||
subprocess.stderr.on('data', (data: any) => {
|
||||
logServer(`[NITRO]::Error: ${data}`)
|
||||
log(`[SERVER]::Error: ${data}`)
|
||||
})
|
||||
|
||||
subprocess.on('close', (code: any) => {
|
||||
logServer(`[NITRO]::Debug: Nitro exited with code: ${code}`)
|
||||
log(`[SERVER]::Debug: Nitro exited with code: ${code}`)
|
||||
subprocess = undefined
|
||||
})
|
||||
|
||||
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'
|
||||
} 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-arm64')
|
||||
} else {
|
||||
binaryFolder = join(binaryFolder, 'mac-x64')
|
||||
}
|
||||
binaryFolder = join(binaryFolder, 'mac-universal')
|
||||
} else {
|
||||
/**
|
||||
* For Linux: linux-cpu, linux-cuda-11-7, linux-cuda-12-0
|
||||
@ -271,7 +271,7 @@ const validateModelStatus = async (): Promise<void> => {
|
||||
retries: 5,
|
||||
retryDelay: 500,
|
||||
}).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 (res.ok) {
|
||||
const body = await res.json()
|
||||
@ -286,7 +286,7 @@ const validateModelStatus = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
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 fetchRetry = fetchRT(fetch)
|
||||
|
||||
@ -300,11 +300,11 @@ const loadLLMModel = async (settings: NitroModelSettings): Promise<Response> =>
|
||||
retryDelay: 500,
|
||||
})
|
||||
.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)
|
||||
})
|
||||
.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)
|
||||
})
|
||||
}
|
||||
@ -327,7 +327,7 @@ export const stopModel = async (_modelId: string) => {
|
||||
})
|
||||
}, 5000)
|
||||
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, {
|
||||
method: 'DELETE',
|
||||
@ -341,7 +341,7 @@ export const stopModel = async (_modelId: string) => {
|
||||
// don't need to do anything, we still kill the subprocess
|
||||
})
|
||||
.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(() =>
|
||||
resolve({
|
||||
message: 'Model stopped',
|
||||
|
||||
@ -11,6 +11,7 @@ export default class Extension {
|
||||
* @property {string} origin Original specification provided to fetch the package.
|
||||
* @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} 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} 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.
|
||||
@ -19,6 +20,7 @@ export default class Extension {
|
||||
origin?: string
|
||||
installOptions: any
|
||||
name?: string
|
||||
productName?: string
|
||||
url?: string
|
||||
version?: string
|
||||
main?: string
|
||||
@ -42,7 +44,7 @@ export default class Extension {
|
||||
const Arborist = require('@npmcli/arborist')
|
||||
const defaultOpts = {
|
||||
version: false,
|
||||
fullMetadata: false,
|
||||
fullMetadata: true,
|
||||
Arborist,
|
||||
}
|
||||
|
||||
@ -77,6 +79,7 @@ export default class Extension {
|
||||
return pacote.manifest(this.specifier, this.installOptions).then((mnf) => {
|
||||
// set the Package properties based on the it's manifest
|
||||
this.name = mnf.name
|
||||
this.productName = mnf.productName as string | undefined
|
||||
this.version = mnf.version
|
||||
this.main = mnf.main
|
||||
this.description = mnf.description
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AppConfiguration } from '../../types'
|
||||
import { AppConfiguration, SettingComponentProps } from '../../types'
|
||||
import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
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) => {
|
||||
if (engineId !== 'openai' && engineId !== 'groq') {
|
||||
return undefined
|
||||
}
|
||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
||||
const filePath = join(directoryPath, `${engineId}.json`)
|
||||
const data = fs.readFileSync(filePath, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
}
|
||||
if (engineId !== 'openai' && engineId !== 'groq') return undefined
|
||||
|
||||
/**
|
||||
* Utility function to get server log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getServerLogPath = (): string => {
|
||||
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 settingDirectoryPath = join(
|
||||
getJanDataFolderPath(),
|
||||
'settings',
|
||||
'@janhq',
|
||||
engineId === 'openai' ? 'inference-openai-extension' : 'inference-groq-extension',
|
||||
'settings.json'
|
||||
)
|
||||
|
||||
/**
|
||||
* Utility function to get app log path
|
||||
*
|
||||
* @returns {string} The log path.
|
||||
*/
|
||||
export const getAppLogPath = (): string => {
|
||||
const appConfigurations = getAppConfigurations()
|
||||
const logFolderPath = join(appConfigurations.data_folder, 'logs')
|
||||
if (!fs.existsSync(logFolderPath)) {
|
||||
fs.mkdirSync(logFolderPath, { recursive: true })
|
||||
const content = fs.readFileSync(settingDirectoryPath, 'utf-8')
|
||||
const settings: SettingComponentProps[] = JSON.parse(content)
|
||||
const apiKeyId = engineId === 'openai' ? 'openai-api-key' : 'groq-api-key'
|
||||
const keySetting = settings.find((setting) => setting.key === apiKeyId)
|
||||
let fullUrl = settings.find((setting) => setting.key === 'chat-completions-endpoint')
|
||||
?.controllerProps.value
|
||||
|
||||
let apiKey = keySetting?.controllerProps.value
|
||||
if (typeof apiKey !== 'string') apiKey = ''
|
||||
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 './download'
|
||||
export * from './log'
|
||||
export * from './logger'
|
||||
export * from './module'
|
||||
export * from './path'
|
||||
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 { physicalCpuCount } from './config'
|
||||
import { log } from './log'
|
||||
import { log } from './logger'
|
||||
|
||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||
const cpu = await physicalCpuCount()
|
||||
const message = `[NITRO]::CPU informations - ${cpu}`
|
||||
log(message)
|
||||
log(`[NITRO]::CPU informations - ${cpu}`)
|
||||
|
||||
return {
|
||||
numCpuPhysicalCore: cpu,
|
||||
|
||||
@ -7,7 +7,7 @@ export enum NativeRoute {
|
||||
openAppDirectory = 'openAppDirectory',
|
||||
openFileExplore = 'openFileExplorer',
|
||||
selectDirectory = 'selectDirectory',
|
||||
selectModelFiles = 'selectModelFiles',
|
||||
selectFiles = 'selectFiles',
|
||||
relaunch = 'relaunch',
|
||||
|
||||
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||
|
||||
@ -9,3 +9,4 @@ export * from './config'
|
||||
export * from './huggingface'
|
||||
export * from './miscellaneous'
|
||||
export * from './api'
|
||||
export * from './setting'
|
||||
|
||||
@ -85,6 +85,8 @@ export enum ErrorCode {
|
||||
|
||||
InsufficientQuota = 'insufficient_quota',
|
||||
|
||||
InvalidRequestError = 'invalid_request_error',
|
||||
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
|
||||
@ -2,4 +2,5 @@ export * from './systemResourceInfo'
|
||||
export * from './promptTemplate'
|
||||
export * from './appUpdate'
|
||||
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 = {
|
||||
gpuSetting: GpuSetting
|
||||
gpuSetting?: GpuSetting
|
||||
osInfo?: OperatingSystemInfo
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,7 @@ export type Model = {
|
||||
/**
|
||||
* The version of the model.
|
||||
*/
|
||||
version: number
|
||||
version: string
|
||||
|
||||
/**
|
||||
* The format of the model.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { GpuSetting, OperatingSystemInfo } from '../miscellaneous'
|
||||
|
||||
/**
|
||||
* Monitoring extension for system monitoring.
|
||||
* @extends BaseExtension
|
||||
@ -14,4 +16,14 @@ export interface MonitoringInterface {
|
||||
* @returns {Promise<any>} A promise that resolves with the current system load.
|
||||
*/
|
||||
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:
|
||||
- minio_data:/data
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
- '9000:9000'
|
||||
- '9001:9001'
|
||||
environment:
|
||||
# Set the root user and password for Minio
|
||||
MINIO_ROOT_USER: minioadmin # This acts as AWS_ACCESS_KEY
|
||||
@ -18,7 +18,7 @@ services:
|
||||
command: server --console-address ":9001" /data
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:9000/minio/health/live']
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
@ -43,12 +43,9 @@ services:
|
||||
|
||||
# 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
|
||||
image: ghcr.io/janhq/jan-server:dev-cpu-latest
|
||||
environment:
|
||||
# Set the AWS access key, secret access key, bucket name, endpoint, and region for app_cpu
|
||||
AWS_ACCESS_KEY_ID: minioadmin
|
||||
@ -61,9 +58,9 @@ services:
|
||||
profiles:
|
||||
- cpu-s3fs
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1337:1337"
|
||||
- "3928:3928"
|
||||
- '3000:3000'
|
||||
- '1337:1337'
|
||||
- '3928:3928'
|
||||
networks:
|
||||
vpcbr:
|
||||
ipv4_address: 10.5.0.3
|
||||
@ -74,15 +71,12 @@ services:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
image: jan-gpu:latest
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
image: ghcr.io/janhq/jan-server:dev-cuda-12.2-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
|
||||
@ -95,29 +89,26 @@ services:
|
||||
profiles:
|
||||
- gpu-s3fs
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "1337:1337"
|
||||
- "3928:3928"
|
||||
- '3000:3000'
|
||||
- '1337:1337'
|
||||
- '3928:3928'
|
||||
networks:
|
||||
vpcbr:
|
||||
ipv4_address: 10.5.0.4
|
||||
|
||||
app_cpu_fs:
|
||||
image: jan:latest
|
||||
image: ghcr.io/janhq/jan-server:dev-cpu-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"
|
||||
- '3000:3000'
|
||||
- '1337:1337'
|
||||
- '3928:3928'
|
||||
networks:
|
||||
vpcbr:
|
||||
ipv4_address: 10.5.0.5
|
||||
@ -128,24 +119,21 @@ services:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
image: jan-gpu:latest
|
||||
- driver: nvidia
|
||||
count: all
|
||||
capabilities: [gpu]
|
||||
image: ghcr.io/janhq/jan-server:dev-cuda-12.2-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"
|
||||
- '3000:3000'
|
||||
- '1337:1337'
|
||||
- '3928:3928'
|
||||
networks:
|
||||
vpcbr:
|
||||
ipv4_address: 10.5.0.6
|
||||
@ -161,10 +149,9 @@ networks:
|
||||
vpcbr:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 10.5.0.0/16
|
||||
gateway: 10.5.0.1
|
||||
|
||||
config:
|
||||
- subnet: 10.5.0.0/16
|
||||
gateway: 10.5.0.1
|
||||
# Usage:
|
||||
# - 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
|
||||
|
||||
@ -6,8 +6,11 @@ import {
|
||||
getJanDataFolderPath,
|
||||
getJanExtensionsPath,
|
||||
init,
|
||||
AppEvent, NativeRoute,
|
||||
AppEvent,
|
||||
NativeRoute,
|
||||
SelectFileProp,
|
||||
} from '@janhq/core/node'
|
||||
import { SelectFileOption } from '@janhq/core/.'
|
||||
|
||||
export function handleAppIPCs() {
|
||||
/**
|
||||
@ -84,23 +87,39 @@ export function handleAppIPCs() {
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.handle(NativeRoute.selectModelFiles, async () => {
|
||||
const mainWindow = windowManager.mainWindow
|
||||
if (!mainWindow) {
|
||||
console.error('No main window found')
|
||||
return
|
||||
}
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||
title: 'Select model files',
|
||||
buttonLabel: 'Select',
|
||||
properties: ['openFile', 'openDirectory', 'multiSelections'],
|
||||
})
|
||||
if (canceled) {
|
||||
return
|
||||
}
|
||||
ipcMain.handle(
|
||||
NativeRoute.selectFiles,
|
||||
async (_event, option?: SelectFileOption) => {
|
||||
const mainWindow = windowManager.mainWindow
|
||||
if (!mainWindow) {
|
||||
console.error('No main window found')
|
||||
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(
|
||||
NativeRoute.hideQuickAskWindow,
|
||||
|
||||
@ -39,6 +39,7 @@ export function handleAppUpdates() {
|
||||
})
|
||||
if (action.response === 0) {
|
||||
trayManager.destroyCurrentTray()
|
||||
windowManager.closeQuickAskWindow()
|
||||
waitingToInstallVersion = _info?.version
|
||||
autoUpdater.quitAndInstall()
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, Tray } from 'electron'
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
|
||||
import { join } from 'path'
|
||||
/**
|
||||
@ -11,7 +11,7 @@ import { getAppConfigurations, log } from '@janhq/core/node'
|
||||
* IPC Handlers
|
||||
**/
|
||||
import { injectHandler } from './handlers/common'
|
||||
import { handleAppUpdates, waitingToInstallVersion } from './handlers/update'
|
||||
import { handleAppUpdates } from './handlers/update'
|
||||
import { handleAppIPCs } from './handlers/native'
|
||||
|
||||
/**
|
||||
@ -24,11 +24,10 @@ import { cleanUpAndQuit } from './utils/clean'
|
||||
import { setupExtensions } from './utils/extension'
|
||||
import { setupCore } from './utils/setup'
|
||||
import { setupReactDevTool } from './utils/dev'
|
||||
import { cleanLogs } from './utils/log'
|
||||
|
||||
import { registerShortcut } from './utils/selectedText'
|
||||
import { trayManager } from './managers/tray'
|
||||
import { logSystemInfo } from './utils/system'
|
||||
import { registerGlobalShortcuts } from './utils/shortcut'
|
||||
|
||||
const preloadPath = join(__dirname, 'preload.js')
|
||||
const rendererPath = join(__dirname, '..', 'renderer')
|
||||
@ -38,8 +37,6 @@ const mainPath = join(rendererPath, 'index.html')
|
||||
const mainUrl = 'http://localhost:3000'
|
||||
const quickAskUrl = `${mainUrl}/search`
|
||||
|
||||
const quickAskHotKey = 'CommandOrControl+J'
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
app
|
||||
@ -60,6 +57,7 @@ app
|
||||
.then(handleAppUpdates)
|
||||
.then(() => process.env.CI !== 'e2e' && createQuickAskWindow())
|
||||
.then(createMainWindow)
|
||||
.then(registerGlobalShortcuts)
|
||||
.then(() => {
|
||||
if (!app.isPackaged) {
|
||||
windowManager.mainWindow?.webContents.openDevTools()
|
||||
@ -76,16 +74,11 @@ app
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(() => cleanLogs())
|
||||
|
||||
app.on('second-instance', (_event, _commandLine, _workingDirectory) => {
|
||||
windowManager.showMainWindow()
|
||||
})
|
||||
|
||||
app.on('ready', () => {
|
||||
registerGlobalShortcuts()
|
||||
})
|
||||
|
||||
app.on('before-quit', function (evt) {
|
||||
trayManager.destroyCurrentTray()
|
||||
})
|
||||
@ -96,7 +89,11 @@ app.once('quit', () => {
|
||||
|
||||
app.once('window-all-closed', () => {
|
||||
// Feature Toggle for Quick Ask
|
||||
if (getAppConfigurations().quick_ask && !waitingToInstallVersion) return
|
||||
if (
|
||||
getAppConfigurations().quick_ask &&
|
||||
!windowManager.isQuickAskWindowDestroyed()
|
||||
)
|
||||
return
|
||||
cleanUpAndQuit()
|
||||
})
|
||||
|
||||
@ -112,26 +109,6 @@ function createMainWindow() {
|
||||
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.
|
||||
*/
|
||||
|
||||
@ -45,7 +45,7 @@ class WindowManager {
|
||||
windowManager.mainWindow?.on('close', function (evt) {
|
||||
// Feature Toggle for Quick Ask
|
||||
if (!getAppConfigurations().quick_ask) return
|
||||
|
||||
|
||||
if (!isAppQuitting) {
|
||||
evt.preventDefault()
|
||||
windowManager.hideMainWindow()
|
||||
@ -93,10 +93,22 @@ class WindowManager {
|
||||
this._quickAskWindowVisible = true
|
||||
}
|
||||
|
||||
closeQuickAskWindow(): void {
|
||||
if (this._quickAskWindow?.isDestroyed()) return
|
||||
this._quickAskWindow?.close()
|
||||
this._quickAskWindow?.destroy()
|
||||
this._quickAskWindow = undefined
|
||||
this._quickAskWindowVisible = false
|
||||
}
|
||||
|
||||
isQuickAskWindowVisible(): boolean {
|
||||
return this._quickAskWindowVisible
|
||||
}
|
||||
|
||||
isQuickAskWindowDestroyed(): boolean {
|
||||
return this._quickAskWindow?.isDestroyed() ?? true
|
||||
}
|
||||
|
||||
expandQuickAskWindow(heightOffset: number): void {
|
||||
const width = quickAskWindowConfig.width!
|
||||
const height = quickAskWindowConfig.height! + heightOffset
|
||||
@ -112,10 +124,18 @@ class WindowManager {
|
||||
}
|
||||
|
||||
cleanUp(): void {
|
||||
this.mainWindow?.destroy()
|
||||
this._quickAskWindow?.destroy()
|
||||
this._quickAskWindowVisible = false
|
||||
this._mainWindowVisible = false
|
||||
if (!this.mainWindow?.isDestroyed()) {
|
||||
this.mainWindow?.close()
|
||||
this.mainWindow?.destroy()
|
||||
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/**/*",
|
||||
"build/**/*.{js,map}",
|
||||
"pre-install",
|
||||
"models/**/*",
|
||||
"docs/**/*",
|
||||
"scripts/**/*",
|
||||
"icons/**/*"
|
||||
],
|
||||
"asarUnpack": [
|
||||
"pre-install",
|
||||
"models",
|
||||
"docs",
|
||||
"scripts",
|
||||
"icons"
|
||||
@ -110,7 +108,8 @@
|
||||
"eslint-plugin-react": "^7.34.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"run-script-os": "^1.1.6",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.3.3",
|
||||
"@reportportal/agent-js-playwright": "^5.1.7"
|
||||
},
|
||||
"installConfig": {
|
||||
"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. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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]
|
||||
>
|
||||
@ -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
|
||||
|
||||
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
|
||||
plugin name, main entry, description and version.
|
||||
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 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
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
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 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>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
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).
|
||||
|
||||
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",
|
||||
"productName": "Jan Assistant Extension",
|
||||
"version": "1.0.1",
|
||||
"description": "This extension enables assistants, including Jan, a default assistant that can call all downloaded models",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -7,12 +7,10 @@ import replace from '@rollup/plugin-replace'
|
||||
|
||||
const packageJson = require('./package.json')
|
||||
|
||||
const pkg = require('./package.json')
|
||||
|
||||
export default [
|
||||
{
|
||||
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')
|
||||
external: [],
|
||||
watch: {
|
||||
@ -20,8 +18,8 @@ export default [
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
preventAssignment: true,
|
||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||
EXTENSION_NAME: JSON.stringify(packageJson.name),
|
||||
VERSION: JSON.stringify(packageJson.version),
|
||||
}),
|
||||
// Allow json resolution
|
||||
@ -36,7 +34,7 @@ export default [
|
||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||
resolve({
|
||||
extensions: ['.js', '.ts', '.svelte'],
|
||||
browser: true
|
||||
browser: true,
|
||||
}),
|
||||
|
||||
// Resolve source maps to the original source
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
declare const NODE: string
|
||||
declare const EXTENSION_NAME: string
|
||||
declare const VERSION: string
|
||||
|
||||
@ -21,7 +21,7 @@ export default class JanAssistantExtension extends AssistantExtension {
|
||||
JanAssistantExtension._homeDir
|
||||
)
|
||||
if (
|
||||
localStorage.getItem(`${EXTENSION_NAME}-version`) !== VERSION ||
|
||||
localStorage.getItem(`${this.name}-version`) !== VERSION ||
|
||||
!assistantDirExist
|
||||
) {
|
||||
if (!assistantDirExist) await fs.mkdir(JanAssistantExtension._homeDir)
|
||||
@ -29,7 +29,7 @@ export default class JanAssistantExtension extends AssistantExtension {
|
||||
// Write assistant metadata
|
||||
await this.createJanAssistant()
|
||||
// Finished migration
|
||||
localStorage.setItem(`${EXTENSION_NAME}-version`, VERSION)
|
||||
localStorage.setItem(`${this.name}-version`, VERSION)
|
||||
// Update the assistant list
|
||||
events.emit(AssistantEvent.OnAssistantsUpdate, {})
|
||||
}
|
||||
|
||||
@ -1,13 +1,36 @@
|
||||
import fs from 'fs'
|
||||
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
|
||||
// Read it manually instead
|
||||
export const readEmbeddingEngine = (engineName: string) => {
|
||||
const engineSettings = fs.readFileSync(
|
||||
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
||||
'utf-8'
|
||||
)
|
||||
return JSON.parse(engineSettings)
|
||||
if (engineName !== 'openai' && engineName !== 'groq') {
|
||||
const engineSettings = fs.readFileSync(
|
||||
path.join(getJanDataFolderPath(), 'engines', `${engineName}.json`),
|
||||
'utf-8'
|
||||
)
|
||||
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",
|
||||
"productName": "Conversational Extension",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables conversations and state persistence via your filesystem",
|
||||
"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
|
||||
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
|
||||
|
||||
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]
|
||||
>
|
||||
@ -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
|
||||
|
||||
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
|
||||
plugin name, main entry, description and version.
|
||||
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 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
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
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 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>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
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).
|
||||
|
||||
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",
|
||||
"productName": "HuggingFace Extension",
|
||||
"version": "1.0.0",
|
||||
"description": "Hugging Face extension for converting HF models to GGUF",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@ -18,7 +18,7 @@ export default [
|
||||
},
|
||||
plugins: [
|
||||
replace({
|
||||
EXTENSION_NAME: JSON.stringify(packageJson.name),
|
||||
preventAssignment: true,
|
||||
NODE_MODULE_PATH: JSON.stringify(
|
||||
`${packageJson.name}/${packageJson.node}`
|
||||
),
|
||||
|
||||
@ -1,2 +1 @@
|
||||
declare const EXTENSION_NAME: string
|
||||
declare const NODE_MODULE_PATH: string
|
||||
|
||||
@ -338,7 +338,7 @@ export default class JanHuggingFaceExtension extends HuggingFaceExtension {
|
||||
|
||||
const metadata: Model = {
|
||||
object: 'model',
|
||||
version: 1,
|
||||
version: '1.0',
|
||||
format: 'gguf',
|
||||
sources: [
|
||||
{
|
||||
|
||||
@ -32,13 +32,9 @@ export const getQuantizeExecutable = (): string => {
|
||||
binaryName = 'quantize.exe'
|
||||
} 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-arm64')
|
||||
} else {
|
||||
binaryFolder = pjoin(binaryFolder, 'mac-x64')
|
||||
}
|
||||
binaryFolder = pjoin(binaryFolder, 'mac-universal')
|
||||
} else {
|
||||
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. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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]
|
||||
>
|
||||
@ -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
|
||||
|
||||
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
|
||||
plugin name, main entry, description and version.
|
||||
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 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
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
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 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>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
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).
|
||||
|
||||
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",
|
||||
"productName": "Groq Inference Engine Extension",
|
||||
"version": "1.0.0",
|
||||
"description": "This extension enables fast Groq chat completion API calls",
|
||||
"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
|
||||
*/
|
||||
|
||||
import {
|
||||
events,
|
||||
fs,
|
||||
AppConfigurationEventName,
|
||||
joinPath,
|
||||
RemoteOAIEngine,
|
||||
} from '@janhq/core'
|
||||
import { join } from 'path'
|
||||
import { RemoteOAIEngine, SettingComponentProps } from '@janhq/core'
|
||||
|
||||
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.
|
||||
* 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 JanInferenceGroqExtension extends RemoteOAIEngine {
|
||||
private readonly _engineDir = 'file://engines'
|
||||
private readonly _engineMetadataFileName = 'groq.json'
|
||||
|
||||
inferenceUrl: string = COMPLETION_URL
|
||||
inferenceUrl: string = ''
|
||||
provider = 'groq'
|
||||
apiKey = ''
|
||||
|
||||
private _engineSettings = {
|
||||
full_url: COMPLETION_URL,
|
||||
api_key: 'gsk-<your key here>',
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to events emitted by the @janhq/core package.
|
||||
*/
|
||||
async onLoad() {
|
||||
override async onLoad(): Promise<void> {
|
||||
super.onLoad()
|
||||
|
||||
if (!(await fs.existsSync(this._engineDir))) {
|
||||
await fs.mkdir(this._engineDir)
|
||||
}
|
||||
// Register Settings
|
||||
this.registerSettings(SETTINGS)
|
||||
this.registerModels(MODELS)
|
||||
|
||||
this.writeDefaultEngineSettings()
|
||||
|
||||
const settingsFilePath = await joinPath([
|
||||
this._engineDir,
|
||||
this._engineMetadataFileName,
|
||||
])
|
||||
|
||||
// Events subscription
|
||||
events.on(
|
||||
AppConfigurationEventName.OnConfigurationUpdate,
|
||||
(settingsKey: string) => {
|
||||
// Update settings on changes
|
||||
if (settingsKey === settingsFilePath) this.writeDefaultEngineSettings()
|
||||
}
|
||||
// Retrieve API Key Setting
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async writeDefaultEngineSettings() {
|
||||
try {
|
||||
const engineFile = join(this._engineDir, this._engineMetadataFileName)
|
||||
if (await fs.existsSync(engineFile)) {
|
||||
const engine = await fs.readFileSync(engineFile, 'utf-8')
|
||||
this._engineSettings =
|
||||
typeof engine === 'object' ? engine : JSON.parse(engine)
|
||||
this.inferenceUrl = this._engineSettings.full_url
|
||||
this.apiKey = this._engineSettings.api_key
|
||||
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 {
|
||||
await fs.writeFileSync(
|
||||
engineFile,
|
||||
JSON.stringify(this._engineSettings, null, 2)
|
||||
)
|
||||
this.inferenceUrl = value
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
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 },
|
||||
@ -17,8 +19,9 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
MODELS: JSON.stringify(modelsJson),
|
||||
SETTINGS: JSON.stringify(settingJson),
|
||||
MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
||||
COMPLETION_URL: JSON.stringify('https://api.groq.com/openai/v1/chat/completions'),
|
||||
}),
|
||||
],
|
||||
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. 🚀
|
||||
|
||||
## Create Your Own Plugin
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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]
|
||||
>
|
||||
@ -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
|
||||
|
||||
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
|
||||
plugin name, main entry, description and version.
|
||||
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 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
|
||||
source code that will be run when your plugin extension functions are invoked. You can replace the
|
||||
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 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>`.
|
||||
|
||||
```typescript
|
||||
import { core } from '@janhq/core'
|
||||
import { events, MessageEvent, MessageRequest } from '@janhq/core'
|
||||
|
||||
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).
|
||||
|
||||
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
|
||||
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",
|
||||
"productName": "Nitro Inference Engine Extension",
|
||||
"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",
|
||||
"node": "dist/node/index.cjs.js",
|
||||
"author": "Jan <service@jan.ai>",
|
||||
@ -9,8 +10,8 @@
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"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: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: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-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": "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",
|
||||
@ -29,6 +30,7 @@
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.11.4",
|
||||
"@types/os-utils": "^0.0.4",
|
||||
@ -47,10 +49,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@janhq/core": "file:../../core",
|
||||
"decompress": "^4.2.1",
|
||||
"fetch-retry": "^5.0.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"tcp-port-used": "^1.0.2",
|
||||
"terminate": "^2.6.1",
|
||||
"ulidx": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -64,6 +68,7 @@
|
||||
"bundleDependencies": [
|
||||
"tcp-port-used",
|
||||
"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