Merge branch 'dev' into feat/changelog-v0.5.16
This commit is contained in:
commit
cd4be34559
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -3,5 +3,5 @@ blank_issues_enabled: true
|
|||||||
|
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: "\1F4AC Jan Discussions"
|
- name: "\1F4AC Jan Discussions"
|
||||||
url: "https://github.com/orgs/janhq/discussions/categories/q-a"
|
url: "https://github.com/orgs/menloresearch/discussions/categories/q-a"
|
||||||
about: "Get help, discuss features & roadmap, and share your projects"
|
about: "Get help, discuss features & roadmap, and share your projects"
|
||||||
46
.github/workflows/jan-electron-build-beta.yml
vendored
46
.github/workflows/jan-electron-build-beta.yml
vendored
@ -9,31 +9,6 @@ jobs:
|
|||||||
get-update-version:
|
get-update-version:
|
||||||
uses: ./.github/workflows/template-get-update-version.yml
|
uses: ./.github/workflows/template-get-update-version.yml
|
||||||
|
|
||||||
create-draft-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
version: ${{ steps.get_version.outputs.version }}
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Extract tag name without v prefix
|
|
||||||
id: get_version
|
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV && echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
|
|
||||||
env:
|
|
||||||
GITHUB_REF: ${{ github.ref }}
|
|
||||||
- name: Create Draft Release
|
|
||||||
id: create_release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref_name }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
name: "${{ env.VERSION }}"
|
|
||||||
draft: true
|
|
||||||
prerelease: false
|
|
||||||
generate_release_notes: true
|
|
||||||
|
|
||||||
build-macos:
|
build-macos:
|
||||||
uses: ./.github/workflows/template-build-macos.yml
|
uses: ./.github/workflows/template-build-macos.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
@ -43,6 +18,8 @@ jobs:
|
|||||||
public_provider: github
|
public_provider: github
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
beta: true
|
beta: true
|
||||||
|
nightly: false
|
||||||
|
cortex_api_port: "39271"
|
||||||
|
|
||||||
build-windows-x64:
|
build-windows-x64:
|
||||||
uses: ./.github/workflows/template-build-windows-x64.yml
|
uses: ./.github/workflows/template-build-windows-x64.yml
|
||||||
@ -53,6 +30,8 @@ jobs:
|
|||||||
public_provider: github
|
public_provider: github
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
beta: true
|
beta: true
|
||||||
|
nightly: false
|
||||||
|
cortex_api_port: "39271"
|
||||||
|
|
||||||
build-linux-x64:
|
build-linux-x64:
|
||||||
uses: ./.github/workflows/template-build-linux-x64.yml
|
uses: ./.github/workflows/template-build-linux-x64.yml
|
||||||
@ -63,9 +42,11 @@ jobs:
|
|||||||
public_provider: github
|
public_provider: github
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
beta: true
|
beta: true
|
||||||
|
nightly: false
|
||||||
|
cortex_api_port: "39271"
|
||||||
|
|
||||||
sync-temp-to-latest:
|
sync-temp-to-latest:
|
||||||
needs: [build-macos, create-draft-release, build-windows-x64, build-linux-x64]
|
needs: [build-macos, build-windows-x64, build-linux-x64]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@ -82,19 +63,15 @@ jobs:
|
|||||||
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
||||||
AWS_EC2_METADATA_DISABLED: "true"
|
AWS_EC2_METADATA_DISABLED: "true"
|
||||||
|
|
||||||
- name: set release to prerelease
|
|
||||||
run: |
|
|
||||||
gh release edit v${{ needs.create-draft-release.outputs.version }} --draft=false --prerelease
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
noti-discord-and-update-url-readme:
|
noti-discord-and-update-url-readme:
|
||||||
needs: [build-macos, create-draft-release, build-windows-x64, build-linux-x64, sync-temp-to-latest]
|
needs: [build-macos, get-update-version, build-windows-x64, build-linux-x64, sync-temp-to-latest]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set version to environment variable
|
- name: Set version to environment variable
|
||||||
run: |
|
run: |
|
||||||
echo "VERSION=${{ needs.create-draft-release.outputs.version }}" >> $GITHUB_ENV
|
VERSION=${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Notify Discord
|
- name: Notify Discord
|
||||||
uses: Ilshidur/action-discord@master
|
uses: Ilshidur/action-discord@master
|
||||||
@ -105,6 +82,5 @@ jobs:
|
|||||||
- macOS Universal: https://delta.jan.ai/beta/jan-beta-mac-universal-{{ VERSION }}.dmg
|
- macOS Universal: https://delta.jan.ai/beta/jan-beta-mac-universal-{{ VERSION }}.dmg
|
||||||
- Linux Deb: https://delta.jan.ai/beta/jan-beta-linux-amd64-{{ VERSION }}.deb
|
- Linux Deb: https://delta.jan.ai/beta/jan-beta-linux-amd64-{{ VERSION }}.deb
|
||||||
- Linux AppImage: https://delta.jan.ai/beta/jan-beta-linux-x86_64-{{ VERSION }}.AppImage
|
- Linux AppImage: https://delta.jan.ai/beta/jan-beta-linux-x86_64-{{ VERSION }}.AppImage
|
||||||
- Github Release URL: https://github.com/janhq/jan/releases/tag/v{{ VERSION }}
|
|
||||||
env:
|
env:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_JAN_BETA }}
|
||||||
12
.github/workflows/jan-electron-build-nightly.yml
vendored
12
.github/workflows/jan-electron-build-nightly.yml
vendored
@ -55,6 +55,9 @@ jobs:
|
|||||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
nightly: true
|
||||||
|
beta: false
|
||||||
|
cortex_api_port: "39261"
|
||||||
|
|
||||||
build-windows-x64:
|
build-windows-x64:
|
||||||
uses: ./.github/workflows/template-build-windows-x64.yml
|
uses: ./.github/workflows/template-build-windows-x64.yml
|
||||||
@ -64,8 +67,9 @@ jobs:
|
|||||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
nightly: true
|
||||||
|
beta: false
|
||||||
|
cortex_api_port: "39261"
|
||||||
build-linux-x64:
|
build-linux-x64:
|
||||||
uses: ./.github/workflows/template-build-linux-x64.yml
|
uses: ./.github/workflows/template-build-linux-x64.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
@ -74,6 +78,9 @@ jobs:
|
|||||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
nightly: true
|
||||||
|
beta: false
|
||||||
|
cortex_api_port: "39261"
|
||||||
|
|
||||||
sync-temp-to-latest:
|
sync-temp-to-latest:
|
||||||
needs: [set-public-provider, build-windows-x64, build-linux-x64, build-macos]
|
needs: [set-public-provider, build-windows-x64, build-linux-x64, build-macos]
|
||||||
@ -141,4 +148,3 @@ jobs:
|
|||||||
RUN_ID=${{ github.run_id }}
|
RUN_ID=${{ github.run_id }}
|
||||||
COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})."
|
COMMENT="This is the build for this pull request. You can download it from the Artifacts section here: [Build URL](https://github.com/${{ github.repository }}/actions/runs/${RUN_ID})."
|
||||||
gh pr comment $PR_URL --body "$COMMENT"
|
gh pr comment $PR_URL --body "$COMMENT"
|
||||||
|
|
||||||
8
.github/workflows/jan-electron-build.yml
vendored
8
.github/workflows/jan-electron-build.yml
vendored
@ -40,6 +40,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
public_provider: github
|
public_provider: github
|
||||||
|
beta: false
|
||||||
|
nightly: false
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
build-windows-x64:
|
build-windows-x64:
|
||||||
@ -49,6 +51,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
public_provider: github
|
public_provider: github
|
||||||
|
beta: false
|
||||||
|
nightly: false
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
build-linux-x64:
|
build-linux-x64:
|
||||||
@ -58,6 +62,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
public_provider: github
|
public_provider: github
|
||||||
|
beta: false
|
||||||
|
nightly: false
|
||||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||||
|
|
||||||
update_release_draft:
|
update_release_draft:
|
||||||
@ -82,4 +88,4 @@ jobs:
|
|||||||
# config-name: my-config.yml
|
# config-name: my-config.yml
|
||||||
# disable-autolabeler: true
|
# disable-autolabeler: true
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@ -36,7 +36,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||||
run: |
|
run: |
|
||||||
curl -s https://api.github.com/repos/janhq/cortex/releases > /tmp/github_api_releases.json
|
curl -s https://api.github.com/repos/menloresearch/cortex/releases > /tmp/github_api_releases.json
|
||||||
latest_prerelease_name=$(cat /tmp/github_api_releases.json | jq -r '.[] | select(.prerelease) | .name' | head -n 1)
|
latest_prerelease_name=$(cat /tmp/github_api_releases.json | jq -r '.[] | select(.prerelease) | .name' | head -n 1)
|
||||||
|
|
||||||
get_asset_count() {
|
get_asset_count() {
|
||||||
@ -89,39 +89,39 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||||
|
|
||||||
- name: Wait for CI to pass
|
- name: Wait for CI to pass
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||||
run: |
|
run: |
|
||||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||||
while true; do
|
while true; do
|
||||||
ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt')
|
ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt')
|
||||||
if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then
|
if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then
|
||||||
echo "CI is still running, waiting..."
|
echo "CI is still running, waiting..."
|
||||||
sleep 60
|
sleep 60
|
||||||
else
|
|
||||||
echo "CI has completed, checking states..."
|
|
||||||
ci_states=$(gh pr checks $pr_number --json state --jq '.[].state')
|
|
||||||
if echo "$ci_states" | grep -vqE "SUCCESS|SKIPPED"; then
|
|
||||||
echo "CI failed, exiting..."
|
|
||||||
exit 1
|
|
||||||
else
|
else
|
||||||
echo "CI passed, merging PR..."
|
echo "CI has completed, checking states..."
|
||||||
break
|
ci_states=$(gh pr checks $pr_number --json state --jq '.[].state')
|
||||||
|
if echo "$ci_states" | grep -vqE "SUCCESS|SKIPPED"; then
|
||||||
|
echo "CI failed, exiting..."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "CI passed, merging PR..."
|
||||||
|
break
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
done
|
||||||
done
|
|
||||||
|
|
||||||
- name: Merge the PR
|
- name: Merge the PR
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||||
run: |
|
run: |
|
||||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||||
gh pr merge $pr_number --merge --admin
|
gh pr merge $pr_number --merge --admin
|
||||||
|
|||||||
39
.github/workflows/template-build-jan-server.yml
vendored
39
.github/workflows/template-build-jan-server.yml
vendored
@ -1,39 +0,0 @@
|
|||||||
name: build-jan-server
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
dockerfile_path:
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
default: './Dockerfile'
|
|
||||||
docker_image_tag:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
default: 'ghcr.io/janhq/jan-server:dev-latest'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
REGISTRY: ghcr.io
|
|
||||||
IMAGE_NAME: janhq/jan-server
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: ${{ env.REGISTRY }}
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push Docker image
|
|
||||||
uses: docker/build-push-action@v3
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ${{ inputs.dockerfile_path }}
|
|
||||||
push: true
|
|
||||||
tags: ${{ inputs.docker_image_tag }}
|
|
||||||
38
.github/workflows/template-build-linux-x64.yml
vendored
38
.github/workflows/template-build-linux-x64.yml
vendored
@ -23,6 +23,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
nightly:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
cortex_api_port:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: null
|
||||||
secrets:
|
secrets:
|
||||||
DELTA_AWS_S3_BUCKET_NAME:
|
DELTA_AWS_S3_BUCKET_NAME:
|
||||||
required: false
|
required: false
|
||||||
@ -43,6 +51,31 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Replace Icons for Beta Build
|
||||||
|
if: inputs.beta == true && inputs.nightly != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
|
- name: Replace Icons for Nightly Build
|
||||||
|
if: inputs.nightly == true && inputs.beta != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
@ -83,7 +116,7 @@ jobs:
|
|||||||
cat ./electron/package.json
|
cat ./electron/package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/package.json
|
mv /tmp/package.json electron/package.json
|
||||||
cat electron/package.json
|
cat electron/package.json
|
||||||
|
|
||||||
@ -115,6 +148,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: '5'
|
AWS_MAX_ATTEMPTS: '5'
|
||||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||||
|
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||||
|
|
||||||
- name: Build and publish app to github
|
- name: Build and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
@ -150,4 +184,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: jan-linux-amd64-${{ inputs.new_version }}-AppImage
|
name: jan-linux-amd64-${{ inputs.new_version }}-AppImage
|
||||||
path: ./electron/dist/*.AppImage
|
path: ./electron/dist/*.AppImage
|
||||||
37
.github/workflows/template-build-macos.yml
vendored
37
.github/workflows/template-build-macos.yml
vendored
@ -23,6 +23,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
nightly:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
cortex_api_port:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: null
|
||||||
secrets:
|
secrets:
|
||||||
DELTA_AWS_S3_BUCKET_NAME:
|
DELTA_AWS_S3_BUCKET_NAME:
|
||||||
required: false
|
required: false
|
||||||
@ -52,6 +60,30 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Replace Icons for Beta Build
|
||||||
|
if: inputs.beta == true && inputs.nightly != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
|
- name: Replace Icons for Nightly Build
|
||||||
|
if: inputs.nightly == true && inputs.beta != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@ -99,7 +131,7 @@ jobs:
|
|||||||
cat ./electron/package.json
|
cat ./electron/package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./package.json
|
cat ./package.json
|
||||||
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/package.json
|
mv /tmp/package.json electron/package.json
|
||||||
cat electron/package.json
|
cat electron/package.json
|
||||||
|
|
||||||
@ -154,6 +186,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: '5'
|
AWS_MAX_ATTEMPTS: '5'
|
||||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||||
|
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||||
|
|
||||||
- name: Build and publish app to github
|
- name: Build and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
@ -197,4 +230,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: jan-mac-universal-${{ inputs.new_version }}
|
name: jan-mac-universal-${{ inputs.new_version }}
|
||||||
path: ./electron/dist/*.dmg
|
path: ./electron/dist/*.dmg
|
||||||
37
.github/workflows/template-build-windows-x64.yml
vendored
37
.github/workflows/template-build-windows-x64.yml
vendored
@ -23,6 +23,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
nightly:
|
||||||
|
required: false
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
cortex_api_port:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: null
|
||||||
secrets:
|
secrets:
|
||||||
DELTA_AWS_S3_BUCKET_NAME:
|
DELTA_AWS_S3_BUCKET_NAME:
|
||||||
required: false
|
required: false
|
||||||
@ -52,6 +60,30 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Replace Icons for Beta Build
|
||||||
|
if: inputs.beta == true && inputs.nightly != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-beta-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-beta.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-beta.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-beta-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
|
- name: Replace Icons for Nightly Build
|
||||||
|
if: inputs.nightly == true && inputs.beta != true
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
rm -rf electron/icons/*
|
||||||
|
|
||||||
|
cp electron/icons_dev/jan-nightly-512x512.png electron/icons/512x512.png
|
||||||
|
cp electron/icons_dev/jan-nightly.ico electron/icons/icon.ico
|
||||||
|
cp electron/icons_dev/jan-nightly.png electron/icons/icon.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray@2x.png electron/icons/icon-tray@2x.png
|
||||||
|
cp electron/icons_dev/jan-nightly-tray.png electron/icons/icon-tray.png
|
||||||
|
|
||||||
- name: Installing node
|
- name: Installing node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
@ -108,7 +140,7 @@ jobs:
|
|||||||
cat ./package.json
|
cat ./package.json
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
cat ./electron/scripts/uninstaller.nsh
|
cat ./electron/scripts/uninstaller.nsh
|
||||||
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "github", "owner": "janhq", "repo": "jan", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
jq '.build.publish = [{"provider": "generic", "url": "https://delta.jan.ai/beta", "channel": "beta"}, {"provider": "s3", "acl": null, "bucket": "${{ secrets.DELTA_AWS_S3_BUCKET_NAME }}", "region": "${{ secrets.DELTA_AWS_REGION}}", "path": "temp-beta", "channel": "beta"}]' electron/package.json > /tmp/package.json
|
||||||
mv /tmp/package.json electron/package.json
|
mv /tmp/package.json electron/package.json
|
||||||
cat electron/package.json
|
cat electron/package.json
|
||||||
|
|
||||||
@ -153,6 +185,7 @@ jobs:
|
|||||||
AWS_MAX_ATTEMPTS: '5'
|
AWS_MAX_ATTEMPTS: '5'
|
||||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||||
|
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||||
|
|
||||||
- name: Build app and publish app to github
|
- name: Build app and publish app to github
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && inputs.public_provider == 'github' && inputs.beta == false
|
||||||
@ -193,4 +226,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: jan-win-x64-${{ inputs.new_version }}
|
name: jan-win-x64-${{ inputs.new_version }}
|
||||||
path: ./electron/dist/*.exe
|
path: ./electron/dist/*.exe
|
||||||
@ -13,46 +13,46 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
new_version: ${{ steps.version_update.outputs.new_version }}
|
new_version: ${{ steps.version_update.outputs.new_version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
uses: dcarbone/install-jq-action@v2.0.1
|
uses: dcarbone/install-jq-action@v2.0.1
|
||||||
|
|
||||||
- name: Get tag
|
- name: Get tag
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
id: tag
|
id: tag
|
||||||
uses: dawidd6/action-get-tag@v1
|
uses: dawidd6/action-get-tag@v1
|
||||||
|
|
||||||
- name: Update app version based on latest release tag with build number
|
- name: Update app version based on latest release tag with build number
|
||||||
id: version_update
|
id: version_update
|
||||||
run: |
|
run: |
|
||||||
# Function to get the latest release tag
|
# Function to get the latest release tag
|
||||||
get_latest_tag() {
|
get_latest_tag() {
|
||||||
local retries=0
|
local retries=0
|
||||||
local max_retries=3
|
local max_retries=3
|
||||||
local tag
|
local tag
|
||||||
while [ $retries -lt $max_retries ]; do
|
while [ $retries -lt $max_retries ]; do
|
||||||
tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name)
|
tag=$(curl -s https://api.github.com/repos/menloresearch/jan/releases/latest | jq -r .tag_name)
|
||||||
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
||||||
echo $tag
|
echo $tag
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
let retries++
|
let retries++
|
||||||
echo "Retrying... ($retries/$max_retries)"
|
echo "Retrying... ($retries/$max_retries)"
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo "Failed to fetch latest tag after $max_retries attempts."
|
echo "Failed to fetch latest tag after $max_retries attempts."
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then
|
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then
|
||||||
echo "Tag detected, set output follow tag"
|
echo "Tag detected, set output follow tag"
|
||||||
echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}"
|
echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}"
|
||||||
else
|
else
|
||||||
# Get the latest release tag from GitHub API
|
# Get the latest release tag from GitHub API
|
||||||
LATEST_TAG=$(get_latest_tag)
|
LATEST_TAG=$(get_latest_tag)
|
||||||
|
|
||||||
# Remove the 'v' and append the build number to the version
|
# Remove the 'v' and append the build number to the version
|
||||||
new_version="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
new_version="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
||||||
echo "New version: $new_version"
|
echo "New version: $new_version"
|
||||||
echo "::set-output name=new_version::$new_version"
|
echo "::set-output name=new_version::$new_version"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -34,7 +34,7 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: "0"
|
fetch-depth: '0'
|
||||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
@ -51,6 +51,6 @@ jobs:
|
|||||||
- macOS Universal: https://delta.jan.ai/nightly/jan-nightly-mac-universal-{{ VERSION }}.dmg
|
- macOS Universal: https://delta.jan.ai/nightly/jan-nightly-mac-universal-{{ VERSION }}.dmg
|
||||||
- Linux Deb: https://delta.jan.ai/nightly/jan-nightly-linux-amd64-{{ VERSION }}.deb
|
- Linux Deb: https://delta.jan.ai/nightly/jan-nightly-linux-amd64-{{ VERSION }}.deb
|
||||||
- Linux AppImage: https://delta.jan.ai/nightly/jan-nightly-linux-x86_64-{{ VERSION }}.AppImage
|
- Linux AppImage: https://delta.jan.ai/nightly/jan-nightly-linux-x86_64-{{ VERSION }}.AppImage
|
||||||
- Github action run: https://github.com/janhq/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
- Github action run: https://github.com/menloresearch/jan/actions/runs/{{ GITHUB_RUN_ID }}
|
||||||
env:
|
env:
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
npx oxlint@latest --fix
|
yarn lint --fix --quiet
|
||||||
@ -6,8 +6,8 @@ First off, thank you for considering contributing to jan. It's people like you t
|
|||||||
|
|
||||||
### Reporting Bugs
|
### Reporting Bugs
|
||||||
|
|
||||||
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/janhq/jan/issues).
|
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/menloresearch/jan/issues).
|
||||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/janhq/jan/issues/new).
|
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/menloresearch/jan/issues/new).
|
||||||
|
|
||||||
### Suggesting Enhancements
|
### Suggesting Enhancements
|
||||||
|
|
||||||
@ -29,4 +29,4 @@ First off, thank you for considering contributing to jan. It's people like you t
|
|||||||
|
|
||||||
## Additional Notes
|
## Additional Notes
|
||||||
|
|
||||||
Thank you for contributing to jan!
|
Thank you for contributing to jan!
|
||||||
|
|||||||
70
README.md
70
README.md
@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/janhq/jan"/>
|
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/menloresearch/jan"/>
|
||||||
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/janhq/jan"/>
|
<img alt="Github Last Commit" src="https://img.shields.io/github/last-commit/menloresearch/jan"/>
|
||||||
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/janhq/jan"/>
|
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/menloresearch/jan"/>
|
||||||
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/janhq/jan"/>
|
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/menloresearch/jan"/>
|
||||||
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
|
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://jan.ai/docs/quickstart">Getting Started</a>
|
<a href="https://jan.ai/docs/quickstart">Getting Started</a>
|
||||||
- <a href="https://jan.ai/docs">Docs</a>
|
- <a href="https://jan.ai/docs">Docs</a>
|
||||||
- <a href="https://github.com/janhq/jan/releases">Changelog</a>
|
- <a href="https://github.com/menloresearch/jan/releases">Changelog</a>
|
||||||
- <a href="https://github.com/janhq/jan/issues">Bug reports</a>
|
- <a href="https://github.com/menloresearch/jan/issues">Bug reports</a>
|
||||||
- <a href="https://discord.gg/AsJ8krTT3N">Discord</a>
|
- <a href="https://discord.gg/AsJ8krTT3N">Discord</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -23,10 +23,9 @@
|
|||||||
⚠️ <b> Jan is currently in Development</b>: Expect breaking changes and bugs!
|
⚠️ <b> Jan is currently in Development</b>: Expect breaking changes and bugs!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
Jan is a ChatGPT-alternative that runs 100% offline on your device. Our goal is to make it easy for a layperson to download and run LLMs and use AI with **full control** and **privacy**.
|
Jan is a ChatGPT-alternative that runs 100% offline on your device. Our goal is to make it easy for a layperson to download and run LLMs and use AI with **full control** and **privacy**.
|
||||||
|
|
||||||
Jan is powered by [Cortex](https://github.com/janhq/cortex.cpp), our embeddable local AI engine that runs on any hardware.
|
Jan is powered by [Cortex](https://github.com/menloresearch/cortex.cpp), our embeddable local AI engine that runs on any hardware.
|
||||||
From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
||||||
|
|
||||||
- [x] NVIDIA GPUs (fast)
|
- [x] NVIDIA GPUs (fast)
|
||||||
@ -36,7 +35,8 @@ From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
|||||||
- [x] Windows x64
|
- [x] Windows x64
|
||||||
|
|
||||||
#### Features:
|
#### Features:
|
||||||
- [Model Library](https://jan.ai/docs/models/manage-models#add-models) with popular LLMs like Llama, Gemma, Mistral, or Qwen
|
|
||||||
|
- [Model Library](https://jan.ai/docs/models/manage-models#add-models) with popular LLMs like Llama, Gemma, Mistral, or Qwen
|
||||||
- Connect to [Remote AI APIs](https://jan.ai/docs/remote-models/openai) like Groq and OpenRouter
|
- Connect to [Remote AI APIs](https://jan.ai/docs/remote-models/openai) like Groq and OpenRouter
|
||||||
- Local API Server with OpenAI-equivalent API
|
- Local API Server with OpenAI-equivalent API
|
||||||
- [Extensions](https://jan.ai/docs/extensions) for customizing Jan
|
- [Extensions](https://jan.ai/docs/extensions) for customizing Jan
|
||||||
@ -54,25 +54,25 @@ From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
|||||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/latest/win-x64'>
|
<a href='https://app.jan.ai/download/latest/win-x64'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/latest/mac-universal'>
|
<a href='https://app.jan.ai/download/latest/mac-universal'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>jan.dmg</b>
|
<b>jan.dmg</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/latest/linux-amd64-deb'>
|
<a href='https://app.jan.ai/download/latest/linux-amd64-deb'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/latest/linux-amd64-appimage'>
|
<a href='https://app.jan.ai/download/latest/linux-amd64-appimage'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -81,25 +81,25 @@ From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
|||||||
<td style="text-align:center"><b>Beta (Preview)</b></td>
|
<td style="text-align:center"><b>Beta (Preview)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/beta/win-x64'>
|
<a href='https://app.jan.ai/download/beta/win-x64'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/beta/mac-universal'>
|
<a href='https://app.jan.ai/download/beta/mac-universal'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>jan.dmg</b>
|
<b>jan.dmg</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/beta/linux-amd64-deb'>
|
<a href='https://app.jan.ai/download/beta/linux-amd64-deb'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/beta/linux-amd64-appimage'>
|
<a href='https://app.jan.ai/download/beta/linux-amd64-appimage'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
@ -108,59 +108,59 @@ From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
|||||||
<td style="text-align:center"><b>Nightly Build (Experimental)</b></td>
|
<td style="text-align:center"><b>Nightly Build (Experimental)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/nightly/win-x64'>
|
<a href='https://app.jan.ai/download/nightly/win-x64'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/nightly/mac-universal'>
|
<a href='https://app.jan.ai/download/nightly/mac-universal'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>jan.dmg</b>
|
<b>jan.dmg</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/nightly/linux-amd64-deb'>
|
<a href='https://app.jan.ai/download/nightly/linux-amd64-deb'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://app.jan.ai/download/nightly/linux-amd64-appimage'>
|
<a href='https://app.jan.ai/download/nightly/linux-amd64-appimage'>
|
||||||
<img src='https://github.com/janhq/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='https://github.com/menloresearch/jan/blob/dev/docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.AppImage</b>
|
<b>jan.AppImage</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
Download the latest version of Jan at https://jan.ai/ or visit the [GitHub Releases](https://github.com/janhq/jan/releases) to download any previous release.
|
Download the latest version of Jan at https://jan.ai/ or visit the [GitHub Releases](https://github.com/menloresearch/jan/releases) to download any previous release.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/c3592fa2-c504-4d9d-a885-7e00122a50f3
|
https://github.com/user-attachments/assets/c3592fa2-c504-4d9d-a885-7e00122a50f3
|
||||||
|
|
||||||
*Real-time Video: Jan v0.5.7 on a Mac M2, 16GB Sonoma 14.2*
|
_Real-time Video: Jan v0.5.7 on a Mac M2, 16GB Sonoma 14.2_
|
||||||
|
|
||||||
## Quicklinks
|
## Quicklinks
|
||||||
|
|
||||||
### Jan
|
### Jan
|
||||||
|
|
||||||
- [Jan Website](https://jan.ai/)
|
- [Jan Website](https://jan.ai/)
|
||||||
- [Jan GitHub](https://github.com/janhq/jan)
|
- [Jan GitHub](https://github.com/menloresearch/jan)
|
||||||
- [Documentation](https://jan.ai/docs)
|
- [Documentation](https://jan.ai/docs)
|
||||||
- [Jan Changelog](https://jan.ai/changelog)
|
- [Jan Changelog](https://jan.ai/changelog)
|
||||||
- [Jan Blog](https://jan.ai/blog)
|
- [Jan Blog](https://jan.ai/blog)
|
||||||
|
|
||||||
### Cortex.cpp
|
### Cortex.cpp
|
||||||
|
|
||||||
Jan is powered by **Cortex.cpp**. It is a C++ command-line interface (CLI) designed as an alternative to [Ollama](https://ollama.com/). By default, it runs on the llama.cpp engine but also supports other engines, including ONNX and TensorRT-LLM, making it a multi-engine platform.
|
Jan is powered by **Cortex.cpp**. It is a C++ command-line interface (CLI) designed as an alternative to [Ollama](https://ollama.com/). By default, it runs on the llama.cpp engine but also supports other engines, including ONNX and TensorRT-LLM, making it a multi-engine platform.
|
||||||
|
|
||||||
|
|
||||||
- [Cortex Website](https://cortex.so/)
|
- [Cortex Website](https://cortex.so/)
|
||||||
- [Cortex GitHub](https://github.com/janhq/cortex.cpp)
|
- [Cortex GitHub](https://github.com/menloresearch/cortex.cpp)
|
||||||
- [Documentation](https://cortex.so/docs/)
|
- [Documentation](https://cortex.so/docs/)
|
||||||
- [Models Library](https://cortex.so/models)
|
- [Models Library](https://cortex.so/models)
|
||||||
- API Reference: *Under development*
|
- API Reference: _Under development_
|
||||||
|
|
||||||
## Requirements for running Jan
|
## Requirements for running Jan
|
||||||
|
|
||||||
- **MacOS**: 13 or higher
|
- **MacOS**: 13 or higher
|
||||||
@ -179,17 +179,17 @@ Jan is powered by **Cortex.cpp**. It is a C++ command-line interface (CLI) desig
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
As Jan is in development mode, you might get stuck on a some common issues:
|
As Jan is in development mode, you might get stuck on a some common issues:
|
||||||
|
|
||||||
- [Troubleshooting a broken build](https://jan.ai/docs/troubleshooting#broken-build)
|
- [Troubleshooting a broken build](https://jan.ai/docs/troubleshooting#broken-build)
|
||||||
- [Troubleshooting NVIDIA GPU](https://jan.ai/docs/troubleshooting#troubleshooting-nvidia-gpu)
|
- [Troubleshooting NVIDIA GPU](https://jan.ai/docs/troubleshooting#troubleshooting-nvidia-gpu)
|
||||||
- [Troubleshooting Something's Amiss](https://jan.ai/docs/troubleshooting#somethings-amiss)
|
- [Troubleshooting Something's Amiss](https://jan.ai/docs/troubleshooting#somethings-amiss)
|
||||||
|
|
||||||
|
|
||||||
If you can't find what you need in our troubleshooting guide, feel free reach out to us for extra help:
|
If you can't find what you need in our troubleshooting guide, feel free reach out to us for extra help:
|
||||||
|
|
||||||
1. Copy your [error logs & device specifications](https://jan.ai/docs/troubleshooting#how-to-get-error-logs).
|
1. Copy your [error logs & device specifications](https://jan.ai/docs/troubleshooting#how-to-get-error-logs).
|
||||||
2. Go to our [Discord](https://discord.com/invite/FTk2MvZwJH) & send it to **#🆘|get-help** channel for further support.
|
2. Go to our [Discord](https://discord.com/invite/FTk2MvZwJH) & send it to **#🆘|get-help** channel for further support.
|
||||||
|
|
||||||
*Check the logs to ensure the information is what you intend to send. Note that we retain your logs for only 24 hours, so report any issues promptly.*
|
_Check the logs to ensure the information is what you intend to send. Note that we retain your logs for only 24 hours, so report any issues promptly._
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@ -206,7 +206,7 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
|||||||
1. **Clone the repository and prepare:**
|
1. **Clone the repository and prepare:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/janhq/jan
|
git clone https://github.com/menloresearch/jan
|
||||||
cd jan
|
cd jan
|
||||||
git checkout -b DESIRED_BRANCH
|
git checkout -b DESIRED_BRANCH
|
||||||
```
|
```
|
||||||
@ -219,8 +219,6 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
|||||||
|
|
||||||
This will start the development server and open the desktop app.
|
This will start the development server and open the desktop app.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### For production build
|
### For production build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -244,7 +242,7 @@ Jan builds on top of other open-source projects:
|
|||||||
|
|
||||||
- Bugs & requests: file a GitHub ticket
|
- Bugs & requests: file a GitHub ticket
|
||||||
- For discussion: join our Discord [here](https://discord.gg/FTk2MvZwJH)
|
- For discussion: join our Discord [here](https://discord.gg/FTk2MvZwJH)
|
||||||
- For business inquiries: email hello@jan.ai
|
- For business inquiries: email hello@jan.ai
|
||||||
- For jobs: please email hr@jan.ai
|
- For jobs: please email hr@jan.ai
|
||||||
|
|
||||||
## Trust & Safety
|
## Trust & Safety
|
||||||
@ -254,7 +252,7 @@ Beware of scams!
|
|||||||
- We will never request your personal information.
|
- We will never request your personal information.
|
||||||
- Our product is completely free; no paid version exists.
|
- Our product is completely free; no paid version exists.
|
||||||
- We do not have a token or ICO.
|
- We do not have a token or ICO.
|
||||||
- We are a [bootstrapped company](https://en.wikipedia.org/wiki/Bootstrapping), and don't have any external investors (*yet*). We're open to exploring opportunities with strategic partners want to tackle [our mission](https://jan.ai/about#mission) together.
|
- We are a [bootstrapped company](https://en.wikipedia.org/wiki/Bootstrapping), and don't have any external investors (_yet_). We're open to exploring opportunities with strategic partners want to tackle [our mission](https://jan.ai/about#mission) together.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<url type="homepage">https://jan.ai/</url>
|
<url type="homepage">https://jan.ai/</url>
|
||||||
<url type="bugtracker">https://github.com/janhq/jan/issues</url>
|
<url type="bugtracker">https://github.com/menloresearch/jan/issues</url>
|
||||||
|
|
||||||
<content_rating type="oars-1.1" />
|
<content_rating type="oars-1.1" />
|
||||||
|
|
||||||
|
|||||||
@ -8,37 +8,38 @@
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Web / extension runtime
|
// Web / extension runtime
|
||||||
import * as core from "@janhq/core";
|
import * as core from '@janhq/core'
|
||||||
|
|
||||||
// Node runtime
|
// Node runtime
|
||||||
import * as node from "@janhq/core/node";
|
import * as node from '@janhq/core/node'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build an Extension
|
## Build an Extension
|
||||||
|
|
||||||
1. Download an extension template, for example, [https://github.com/janhq/extension-template](https://github.com/janhq/extension-template).
|
1. Download an extension template, for example, [https://github.com/menloresearch/extension-template](https://github.com/menloresearch/extension-template).
|
||||||
|
|
||||||
2. Update the source code:
|
2. Update the source code:
|
||||||
|
|
||||||
1. Open `index.ts` in your code editor.
|
1. Open `index.ts` in your code editor.
|
||||||
2. Rename the extension class from `SampleExtension` to your preferred extension name.
|
2. Rename the extension class from `SampleExtension` to your preferred extension name.
|
||||||
3. Import modules from the core package.
|
3. Import modules from the core package.
|
||||||
```ts
|
```ts
|
||||||
import * as core from "@janhq/core";
|
import * as core from '@janhq/core'
|
||||||
```
|
```
|
||||||
4. In the `onLoad()` method, add your code:
|
4. In the `onLoad()` method, add your code:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Example of listening to app events and providing customized inference logic:
|
// Example of listening to app events and providing customized inference logic:
|
||||||
import * as core from "@janhq/core";
|
import * as core from '@janhq/core'
|
||||||
|
|
||||||
export default class MyExtension extends BaseExtension {
|
export default class MyExtension extends BaseExtension {
|
||||||
// On extension load
|
// On extension load
|
||||||
onLoad() {
|
onLoad() {
|
||||||
core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this));
|
core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customized inference logic
|
// Customized inference logic
|
||||||
private static inference(incomingMessage: MessageRequestData) {
|
private static inference(incomingMessage: MessageRequestData) {
|
||||||
|
|
||||||
// Prepare customized message content
|
// Prepare customized message content
|
||||||
const content: ThreadContent = {
|
const content: ThreadContent = {
|
||||||
type: ContentType.Text,
|
type: ContentType.Text,
|
||||||
@ -46,16 +47,17 @@ import * as node from "@janhq/core/node";
|
|||||||
value: "I'm Jan Assistant!",
|
value: "I'm Jan Assistant!",
|
||||||
annotations: [],
|
annotations: [],
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
// Modify message and send out
|
// Modify message and send out
|
||||||
const outGoingMessage: ThreadMessage = {
|
const outGoingMessage: ThreadMessage = {
|
||||||
...incomingMessage,
|
...incomingMessage,
|
||||||
content
|
content,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Build the extension:
|
3. Build the extension:
|
||||||
1. Navigate to the extension directory.
|
1. Navigate to the extension directory.
|
||||||
2. Install dependencies.
|
2. Install dependencies.
|
||||||
@ -66,4 +68,4 @@ import * as node from "@janhq/core/node";
|
|||||||
```bash
|
```bash
|
||||||
yarn build
|
yarn build
|
||||||
```
|
```
|
||||||
4. Select the generated .tgz from Jan > Settings > Extension > Manual Installation.
|
4. Select the generated .tgz from Jan > Settings > Extension > Manual Installation.
|
||||||
|
|||||||
@ -25,7 +25,6 @@ export default defineConfig([
|
|||||||
'@types/pacote',
|
'@types/pacote',
|
||||||
'@npmcli/arborist',
|
'@npmcli/arborist',
|
||||||
'ulidx',
|
'ulidx',
|
||||||
'node-fetch',
|
|
||||||
'fs',
|
'fs',
|
||||||
'request',
|
'request',
|
||||||
'crypto',
|
'crypto',
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { openExternalUrl } from './core'
|
|||||||
import { joinPath } from './core'
|
import { joinPath } from './core'
|
||||||
import { openFileExplorer } from './core'
|
import { openFileExplorer } from './core'
|
||||||
import { getJanDataFolderPath } from './core'
|
import { getJanDataFolderPath } from './core'
|
||||||
import { abortDownload } from './core'
|
|
||||||
import { executeOnMain } from './core'
|
import { executeOnMain } from './core'
|
||||||
|
|
||||||
describe('test core apis', () => {
|
describe('test core apis', () => {
|
||||||
@ -53,18 +52,6 @@ describe('test core apis', () => {
|
|||||||
expect(result).toBe('/path/to/jan/data')
|
expect(result).toBe('/path/to/jan/data')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should abort download', async () => {
|
|
||||||
const fileName = 'testFile'
|
|
||||||
globalThis.core = {
|
|
||||||
api: {
|
|
||||||
abortDownload: jest.fn().mockResolvedValue('aborted'),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const result = await abortDownload(fileName)
|
|
||||||
expect(globalThis.core.api.abortDownload).toHaveBeenCalledWith(fileName)
|
|
||||||
expect(result).toBe('aborted')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should execute function on main process', async () => {
|
it('should execute function on main process', async () => {
|
||||||
const extension = 'testExtension'
|
const extension = 'testExtension'
|
||||||
const method = 'testMethod'
|
const method = 'testMethod'
|
||||||
|
|||||||
@ -1,9 +1,4 @@
|
|||||||
import {
|
import { SystemInformation } from '../types'
|
||||||
DownloadRequest,
|
|
||||||
FileStat,
|
|
||||||
NetworkConfig,
|
|
||||||
SystemInformation,
|
|
||||||
} from '../types'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a extension module function in main process
|
* Execute a extension module function in main process
|
||||||
@ -14,42 +9,19 @@ import {
|
|||||||
* @returns Promise<any>
|
* @returns Promise<any>
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
const executeOnMain: (
|
const executeOnMain: (extension: string, method: string, ...args: any[]) => Promise<any> = (
|
||||||
extension: string,
|
extension,
|
||||||
method: string,
|
method,
|
||||||
...args: any[]
|
...args
|
||||||
) => Promise<any> = (extension, method, ...args) =>
|
) => globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args)
|
||||||
globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Downloads a file from a URL and saves it to the local file system.
|
|
||||||
*
|
|
||||||
* @param {DownloadRequest} downloadRequest - The request to download the file.
|
|
||||||
* @param {NetworkConfig} network - Optional object to specify proxy/whether to ignore SSL certificates.
|
|
||||||
*
|
|
||||||
* @returns {Promise<any>} A promise that resolves when the file is downloaded.
|
|
||||||
*/
|
|
||||||
const downloadFile: (
|
|
||||||
downloadRequest: DownloadRequest,
|
|
||||||
network?: NetworkConfig
|
|
||||||
) => Promise<any> = (downloadRequest, network) =>
|
|
||||||
globalThis.core?.api?.downloadFile(downloadRequest, network)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Aborts the download of a specific file.
|
|
||||||
* @param {string} fileName - The name of the file whose download is to be aborted.
|
|
||||||
* @returns {Promise<any>} A promise that resolves when the download has been aborted.
|
|
||||||
*/
|
|
||||||
const abortDownload: (fileName: string) => Promise<any> = (fileName) =>
|
|
||||||
globalThis.core.api?.abortDownload(fileName)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets Jan's data folder path.
|
* Gets Jan's data folder path.
|
||||||
*
|
*
|
||||||
* @returns {Promise<string>} A Promise that resolves with Jan's data folder path.
|
* @returns {Promise<string>} A Promise that resolves with Jan's data folder path.
|
||||||
*/
|
*/
|
||||||
const getJanDataFolderPath = (): Promise<string> =>
|
const getJanDataFolderPath = (): Promise<string> => globalThis.core.api?.getJanDataFolderPath()
|
||||||
globalThis.core.api?.getJanDataFolderPath()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the file explorer at a specific path.
|
* Opens the file explorer at a specific path.
|
||||||
@ -72,16 +44,14 @@ const joinPath: (paths: string[]) => Promise<string> = (paths) =>
|
|||||||
* @param path - The file path to retrieve dirname.
|
* @param path - The file path to retrieve dirname.
|
||||||
* @returns {Promise<string>} A promise that resolves the dirname.
|
* @returns {Promise<string>} A promise that resolves the dirname.
|
||||||
*/
|
*/
|
||||||
const dirName: (path: string) => Promise<string> = (path) =>
|
const dirName: (path: string) => Promise<string> = (path) => globalThis.core.api?.dirName(path)
|
||||||
globalThis.core.api?.dirName(path)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the basename from an url.
|
* Retrieve the basename from an url.
|
||||||
* @param path - The path to retrieve.
|
* @param path - The path to retrieve.
|
||||||
* @returns {Promise<string>} A promise that resolves with the basename.
|
* @returns {Promise<string>} A promise that resolves with the basename.
|
||||||
*/
|
*/
|
||||||
const baseName: (paths: string) => Promise<string> = (path) =>
|
const baseName: (paths: string) => Promise<string> = (path) => globalThis.core.api?.baseName(path)
|
||||||
globalThis.core.api?.baseName(path)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens an external URL in the default web browser.
|
* Opens an external URL in the default web browser.
|
||||||
@ -97,15 +67,13 @@ const openExternalUrl: (url: string) => Promise<any> = (url) =>
|
|||||||
*
|
*
|
||||||
* @returns {Promise<string>} - A promise that resolves with the resource path.
|
* @returns {Promise<string>} - A promise that resolves with the resource path.
|
||||||
*/
|
*/
|
||||||
const getResourcePath: () => Promise<string> = () =>
|
const getResourcePath: () => Promise<string> = () => globalThis.core.api?.getResourcePath()
|
||||||
globalThis.core.api?.getResourcePath()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the user's home path.
|
* Gets the user's home path.
|
||||||
* @returns return user's home path
|
* @returns return user's home path
|
||||||
*/
|
*/
|
||||||
const getUserHomePath = (): Promise<string> =>
|
const getUserHomePath = (): Promise<string> => globalThis.core.api?.getUserHomePath()
|
||||||
globalThis.core.api?.getUserHomePath()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log to file from browser processes.
|
* Log to file from browser processes.
|
||||||
@ -123,10 +91,8 @@ const log: (message: string, fileName?: string) => void = (message, fileName) =>
|
|||||||
*
|
*
|
||||||
* @returns {Promise<boolean>} - A promise that resolves with a boolean indicating whether the path is a subdirectory.
|
* @returns {Promise<boolean>} - A promise that resolves with a boolean indicating whether the path is a subdirectory.
|
||||||
*/
|
*/
|
||||||
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (
|
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (from: string, to: string) =>
|
||||||
from: string,
|
globalThis.core.api?.isSubdirectory(from, to)
|
||||||
to: string
|
|
||||||
) => globalThis.core.api?.isSubdirectory(from, to)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get system information
|
* Get system information
|
||||||
@ -159,8 +125,6 @@ export type RegisterExtensionPoint = (
|
|||||||
*/
|
*/
|
||||||
export {
|
export {
|
||||||
executeOnMain,
|
executeOnMain,
|
||||||
downloadFile,
|
|
||||||
abortDownload,
|
|
||||||
getJanDataFolderPath,
|
getJanDataFolderPath,
|
||||||
openFileExplorer,
|
openFileExplorer,
|
||||||
getResourcePath,
|
getResourcePath,
|
||||||
|
|||||||
@ -39,11 +39,6 @@ describe('BaseExtension', () => {
|
|||||||
expect(baseExtension.onUnload).toBeDefined()
|
expect(baseExtension.onUnload).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have installationState() return "NotRequired"', async () => {
|
|
||||||
const installationState = await baseExtension.installationState()
|
|
||||||
expect(installationState).toBe('NotRequired')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should install the extension', async () => {
|
it('should install the extension', async () => {
|
||||||
await baseExtension.install()
|
await baseExtension.install()
|
||||||
// Add your assertions here
|
// Add your assertions here
|
||||||
@ -84,11 +79,6 @@ describe('BaseExtension', () => {
|
|||||||
expect(baseExtension.onUnload).toBeDefined()
|
expect(baseExtension.onUnload).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should have installationState() return "NotRequired"', async () => {
|
|
||||||
const installationState = await baseExtension.installationState()
|
|
||||||
expect(installationState).toBe('NotRequired')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should install the extension', async () => {
|
it('should install the extension', async () => {
|
||||||
await baseExtension.install()
|
await baseExtension.install()
|
||||||
// Add your assertions here
|
// Add your assertions here
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export enum ExtensionTypeEnum {
|
|||||||
SystemMonitoring = 'systemMonitoring',
|
SystemMonitoring = 'systemMonitoring',
|
||||||
HuggingFace = 'huggingFace',
|
HuggingFace = 'huggingFace',
|
||||||
Engine = 'engine',
|
Engine = 'engine',
|
||||||
|
Hardware = 'hardware',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtensionType {
|
export interface ExtensionType {
|
||||||
@ -23,17 +24,6 @@ export interface Compatibility {
|
|||||||
version: string
|
version: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_INSTALLATION_STATE = [
|
|
||||||
'NotRequired', // not required.
|
|
||||||
'Installed', // require and installed. Good to go.
|
|
||||||
'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
|
|
||||||
export type InstallationState = InstallationStateTuple[number]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a base extension.
|
* Represents a base extension.
|
||||||
* This class should be extended by any class that represents an extension.
|
* This class should be extended by any class that represents an extension.
|
||||||
@ -174,15 +164,6 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine if the prerequisites for the extension are installed.
|
|
||||||
*
|
|
||||||
* @returns {boolean} true if the prerequisites are installed, false otherwise.
|
|
||||||
*/
|
|
||||||
async installationState(): Promise<InstallationState> {
|
|
||||||
return 'NotRequired'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install the prerequisites for the extension.
|
* Install the prerequisites for the extension.
|
||||||
*
|
*
|
||||||
@ -227,7 +208,7 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
|
|
||||||
const settings = await this.getSettings()
|
const settings = await this.getSettings()
|
||||||
|
|
||||||
const updatedSettings = settings.map((setting) => {
|
let updatedSettings = settings.map((setting) => {
|
||||||
const updatedSetting = componentProps.find(
|
const updatedSetting = componentProps.find(
|
||||||
(componentProp) => componentProp.key === setting.key
|
(componentProp) => componentProp.key === setting.key
|
||||||
)
|
)
|
||||||
@ -237,13 +218,20 @@ export abstract class BaseExtension implements ExtensionType {
|
|||||||
return setting
|
return setting
|
||||||
})
|
})
|
||||||
|
|
||||||
const settingPath = await joinPath([
|
if (!updatedSettings.length) updatedSettings = componentProps as SettingComponentProps[]
|
||||||
|
|
||||||
|
const settingFolder = await joinPath([
|
||||||
await getJanDataFolderPath(),
|
await getJanDataFolderPath(),
|
||||||
this.settingFolderName,
|
this.settingFolderName,
|
||||||
this.name,
|
this.name,
|
||||||
this.settingFileName,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if (!(await fs.existsSync(settingFolder))) {
|
||||||
|
await fs.mkdir(settingFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
const settingPath = await joinPath([settingFolder, this.settingFileName])
|
||||||
|
|
||||||
await fs.writeFileSync(settingPath, JSON.stringify(updatedSettings, null, 2))
|
await fs.writeFileSync(settingPath, JSON.stringify(updatedSettings, null, 2))
|
||||||
|
|
||||||
updatedSettings.forEach((setting) => {
|
updatedSettings.forEach((setting) => {
|
||||||
|
|||||||
252
core/src/browser/extensions/conversational.test.ts
Normal file
252
core/src/browser/extensions/conversational.test.ts
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import { ConversationalExtension } from './conversational'
|
||||||
|
import { ExtensionTypeEnum } from '../extension'
|
||||||
|
import { Thread, ThreadAssistantInfo, ThreadMessage } from '../../types'
|
||||||
|
|
||||||
|
// Mock implementation of ConversationalExtension
|
||||||
|
class MockConversationalExtension extends ConversationalExtension {
|
||||||
|
private threads: Thread[] = []
|
||||||
|
private messages: { [threadId: string]: ThreadMessage[] } = {}
|
||||||
|
private assistants: { [threadId: string]: ThreadAssistantInfo } = {}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('http://mock-url.com', 'mock-extension', 'Mock Extension', true, 'A mock extension', '1.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnload(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
async listThreads(): Promise<Thread[]> {
|
||||||
|
return this.threads
|
||||||
|
}
|
||||||
|
|
||||||
|
async createThread(thread: Partial<Thread>): Promise<Thread> {
|
||||||
|
const newThread: Thread = {
|
||||||
|
id: thread.id || `thread-${Date.now()}`,
|
||||||
|
name: thread.name || 'New Thread',
|
||||||
|
createdAt: thread.createdAt || new Date().toISOString(),
|
||||||
|
updatedAt: thread.updatedAt || new Date().toISOString(),
|
||||||
|
}
|
||||||
|
this.threads.push(newThread)
|
||||||
|
this.messages[newThread.id] = []
|
||||||
|
return newThread
|
||||||
|
}
|
||||||
|
|
||||||
|
async modifyThread(thread: Thread): Promise<void> {
|
||||||
|
const index = this.threads.findIndex(t => t.id === thread.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.threads[index] = thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteThread(threadId: string): Promise<void> {
|
||||||
|
this.threads = this.threads.filter(t => t.id !== threadId)
|
||||||
|
delete this.messages[threadId]
|
||||||
|
delete this.assistants[threadId]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createMessage(message: Partial<ThreadMessage>): Promise<ThreadMessage> {
|
||||||
|
if (!message.threadId) throw new Error('Thread ID is required')
|
||||||
|
|
||||||
|
const newMessage: ThreadMessage = {
|
||||||
|
id: message.id || `message-${Date.now()}`,
|
||||||
|
threadId: message.threadId,
|
||||||
|
content: message.content || '',
|
||||||
|
role: message.role || 'user',
|
||||||
|
createdAt: message.createdAt || new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.messages[message.threadId]) {
|
||||||
|
this.messages[message.threadId] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messages[message.threadId].push(newMessage)
|
||||||
|
return newMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMessage(threadId: string, messageId: string): Promise<void> {
|
||||||
|
if (this.messages[threadId]) {
|
||||||
|
this.messages[threadId] = this.messages[threadId].filter(m => m.id !== messageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listMessages(threadId: string): Promise<ThreadMessage[]> {
|
||||||
|
return this.messages[threadId] || []
|
||||||
|
}
|
||||||
|
|
||||||
|
async getThreadAssistant(threadId: string): Promise<ThreadAssistantInfo> {
|
||||||
|
return this.assistants[threadId] || { modelId: '', threadId }
|
||||||
|
}
|
||||||
|
|
||||||
|
async createThreadAssistant(
|
||||||
|
threadId: string,
|
||||||
|
assistant: ThreadAssistantInfo
|
||||||
|
): Promise<ThreadAssistantInfo> {
|
||||||
|
this.assistants[threadId] = assistant
|
||||||
|
return assistant
|
||||||
|
}
|
||||||
|
|
||||||
|
async modifyThreadAssistant(
|
||||||
|
threadId: string,
|
||||||
|
assistant: ThreadAssistantInfo
|
||||||
|
): Promise<ThreadAssistantInfo> {
|
||||||
|
this.assistants[threadId] = assistant
|
||||||
|
return assistant
|
||||||
|
}
|
||||||
|
|
||||||
|
async modifyMessage(message: ThreadMessage): Promise<ThreadMessage> {
|
||||||
|
if (!this.messages[message.threadId]) return message
|
||||||
|
|
||||||
|
const index = this.messages[message.threadId].findIndex(m => m.id === message.id)
|
||||||
|
if (index !== -1) {
|
||||||
|
this.messages[message.threadId][index] = message
|
||||||
|
}
|
||||||
|
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ConversationalExtension', () => {
|
||||||
|
let extension: MockConversationalExtension
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
extension = new MockConversationalExtension()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return the correct extension type', () => {
|
||||||
|
expect(extension.type()).toBe(ExtensionTypeEnum.Conversational)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should create and list threads', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
expect(thread.name).toBe('Test Thread')
|
||||||
|
|
||||||
|
const threads = await extension.listThreads()
|
||||||
|
expect(threads).toHaveLength(1)
|
||||||
|
expect(threads[0].id).toBe(thread.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should modify thread', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
const modifiedThread = { ...thread, name: 'Modified Thread' }
|
||||||
|
|
||||||
|
await extension.modifyThread(modifiedThread)
|
||||||
|
|
||||||
|
const threads = await extension.listThreads()
|
||||||
|
expect(threads[0].name).toBe('Modified Thread')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should delete thread', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
await extension.deleteThread(thread.id)
|
||||||
|
|
||||||
|
const threads = await extension.listThreads()
|
||||||
|
expect(threads).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should create and list messages', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const message = await extension.createMessage({
|
||||||
|
threadId: thread.id,
|
||||||
|
content: 'Test message',
|
||||||
|
role: 'user'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(message.content).toBe('Test message')
|
||||||
|
|
||||||
|
const messages = await extension.listMessages(thread.id)
|
||||||
|
expect(messages).toHaveLength(1)
|
||||||
|
expect(messages[0].id).toBe(message.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should modify message', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const message = await extension.createMessage({
|
||||||
|
threadId: thread.id,
|
||||||
|
content: 'Test message',
|
||||||
|
role: 'user'
|
||||||
|
})
|
||||||
|
|
||||||
|
const modifiedMessage = { ...message, content: 'Modified message' }
|
||||||
|
|
||||||
|
await extension.modifyMessage(modifiedMessage)
|
||||||
|
|
||||||
|
const messages = await extension.listMessages(thread.id)
|
||||||
|
expect(messages[0].content).toBe('Modified message')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should delete message', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const message = await extension.createMessage({
|
||||||
|
threadId: thread.id,
|
||||||
|
content: 'Test message',
|
||||||
|
role: 'user'
|
||||||
|
})
|
||||||
|
|
||||||
|
await extension.deleteMessage(thread.id, message.id)
|
||||||
|
|
||||||
|
const messages = await extension.listMessages(thread.id)
|
||||||
|
expect(messages).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should create and get thread assistant', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const assistant: ThreadAssistantInfo = {
|
||||||
|
threadId: thread.id,
|
||||||
|
modelId: 'test-model'
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.createThreadAssistant(thread.id, assistant)
|
||||||
|
|
||||||
|
const retrievedAssistant = await extension.getThreadAssistant(thread.id)
|
||||||
|
expect(retrievedAssistant.modelId).toBe('test-model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should modify thread assistant', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const assistant: ThreadAssistantInfo = {
|
||||||
|
threadId: thread.id,
|
||||||
|
modelId: 'test-model'
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.createThreadAssistant(thread.id, assistant)
|
||||||
|
|
||||||
|
const modifiedAssistant: ThreadAssistantInfo = {
|
||||||
|
threadId: thread.id,
|
||||||
|
modelId: 'modified-model'
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.modifyThreadAssistant(thread.id, modifiedAssistant)
|
||||||
|
|
||||||
|
const retrievedAssistant = await extension.getThreadAssistant(thread.id)
|
||||||
|
expect(retrievedAssistant.modelId).toBe('modified-model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should delete thread assistant when thread is deleted', async () => {
|
||||||
|
const thread = await extension.createThread({ name: 'Test Thread' })
|
||||||
|
|
||||||
|
const assistant: ThreadAssistantInfo = {
|
||||||
|
threadId: thread.id,
|
||||||
|
modelId: 'test-model'
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.createThreadAssistant(thread.id, assistant)
|
||||||
|
await extension.deleteThread(thread.id)
|
||||||
|
|
||||||
|
// Creating a new thread with the same ID to test if assistant was deleted
|
||||||
|
const newThread = await extension.createThread({ id: thread.id, name: 'New Thread' })
|
||||||
|
const retrievedAssistant = await extension.getThreadAssistant(newThread.id)
|
||||||
|
|
||||||
|
expect(retrievedAssistant.modelId).toBe('')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -3,6 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
import { EngineManager } from './EngineManager'
|
import { EngineManager } from './EngineManager'
|
||||||
import { AIEngine } from './AIEngine'
|
import { AIEngine } from './AIEngine'
|
||||||
|
import { InferenceEngine } from '../../../types'
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
class MockAIEngine implements AIEngine {
|
class MockAIEngine implements AIEngine {
|
||||||
@ -40,4 +41,69 @@ describe('EngineManager', () => {
|
|||||||
const retrievedEngine = engineManager.get<MockAIEngine>('nonExistentProvider')
|
const retrievedEngine = engineManager.get<MockAIEngine>('nonExistentProvider')
|
||||||
expect(retrievedEngine).toBeUndefined()
|
expect(retrievedEngine).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('cortex engine migration', () => {
|
||||||
|
test('should map nitro to cortex engine', () => {
|
||||||
|
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||||
|
// @ts-ignore
|
||||||
|
engineManager.register(cortexEngine)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.nitro)
|
||||||
|
expect(retrievedEngine).toBe(cortexEngine)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should map cortex_llamacpp to cortex engine', () => {
|
||||||
|
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||||
|
// @ts-ignore
|
||||||
|
engineManager.register(cortexEngine)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_llamacpp)
|
||||||
|
expect(retrievedEngine).toBe(cortexEngine)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should map cortex_onnx to cortex engine', () => {
|
||||||
|
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||||
|
// @ts-ignore
|
||||||
|
engineManager.register(cortexEngine)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_onnx)
|
||||||
|
expect(retrievedEngine).toBe(cortexEngine)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should map cortex_tensorrtllm to cortex engine', () => {
|
||||||
|
const cortexEngine = new MockAIEngine(InferenceEngine.cortex)
|
||||||
|
// @ts-ignore
|
||||||
|
engineManager.register(cortexEngine)
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const retrievedEngine = engineManager.get<MockAIEngine>(InferenceEngine.cortex_tensorrtllm)
|
||||||
|
expect(retrievedEngine).toBe(cortexEngine)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('singleton instance', () => {
|
||||||
|
test('should return the window.core.engineManager if available', () => {
|
||||||
|
const mockEngineManager = new EngineManager()
|
||||||
|
// @ts-ignore
|
||||||
|
window.core = { engineManager: mockEngineManager }
|
||||||
|
|
||||||
|
const instance = EngineManager.instance()
|
||||||
|
expect(instance).toBe(mockEngineManager)
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.core
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should create a new instance if window.core.engineManager is not available', () => {
|
||||||
|
// @ts-ignore
|
||||||
|
delete window.core
|
||||||
|
|
||||||
|
const instance = EngineManager.instance()
|
||||||
|
expect(instance).toBeInstanceOf(EngineManager)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -38,8 +38,14 @@ describe('OAIEngine', () => {
|
|||||||
|
|
||||||
it('should subscribe to events on load', () => {
|
it('should subscribe to events on load', () => {
|
||||||
engine.onLoad()
|
engine.onLoad()
|
||||||
expect(events.on).toHaveBeenCalledWith(MessageEvent.OnMessageSent, expect.any(Function))
|
expect(events.on).toHaveBeenCalledWith(
|
||||||
expect(events.on).toHaveBeenCalledWith(InferenceEvent.OnInferenceStopped, expect.any(Function))
|
MessageEvent.OnMessageSent,
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
|
expect(events.on).toHaveBeenCalledWith(
|
||||||
|
InferenceEvent.OnInferenceStopped,
|
||||||
|
expect.any(Function)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle inference request', async () => {
|
it('should handle inference request', async () => {
|
||||||
@ -77,7 +83,12 @@ describe('OAIEngine', () => {
|
|||||||
expect(events.emit).toHaveBeenCalledWith(
|
expect(events.emit).toHaveBeenCalledWith(
|
||||||
MessageEvent.OnMessageUpdate,
|
MessageEvent.OnMessageUpdate,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
content: [{ type: ContentType.Text, text: { value: 'test response', annotations: [] } }],
|
content: [
|
||||||
|
{
|
||||||
|
type: ContentType.Text,
|
||||||
|
text: { value: 'test response', annotations: [] },
|
||||||
|
},
|
||||||
|
],
|
||||||
status: MessageStatus.Ready,
|
status: MessageStatus.Ready,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -101,11 +112,10 @@ describe('OAIEngine', () => {
|
|||||||
|
|
||||||
await engine.inference(data)
|
await engine.inference(data)
|
||||||
|
|
||||||
expect(events.emit).toHaveBeenCalledWith(
|
expect(events.emit).toHaveBeenLastCalledWith(
|
||||||
MessageEvent.OnMessageUpdate,
|
MessageEvent.OnMessageUpdate,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
content: [{ type: ContentType.Text, text: { value: 'test error', annotations: [] } }],
|
status: 'error',
|
||||||
status: MessageStatus.Error,
|
|
||||||
error_code: 500,
|
error_code: 500,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@ -42,7 +42,9 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
*/
|
*/
|
||||||
override onLoad() {
|
override onLoad() {
|
||||||
super.onLoad()
|
super.onLoad()
|
||||||
events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => this.inference(data))
|
events.on(MessageEvent.OnMessageSent, (data: MessageRequest) =>
|
||||||
|
this.inference(data)
|
||||||
|
)
|
||||||
events.on(InferenceEvent.OnInferenceStopped, () => this.stopInference())
|
events.on(InferenceEvent.OnInferenceStopped, () => this.stopInference())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +130,9 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
},
|
},
|
||||||
complete: async () => {
|
complete: async () => {
|
||||||
message.status = message.content.length ? MessageStatus.Ready : MessageStatus.Error
|
message.status = message.content.length
|
||||||
|
? MessageStatus.Ready
|
||||||
|
: MessageStatus.Error
|
||||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||||
},
|
},
|
||||||
error: async (err: any) => {
|
error: async (err: any) => {
|
||||||
@ -141,7 +145,10 @@ export abstract class OAIEngine extends AIEngine {
|
|||||||
message.content[0] = {
|
message.content[0] = {
|
||||||
type: ContentType.Text,
|
type: ContentType.Text,
|
||||||
text: {
|
text: {
|
||||||
value: err.message,
|
value:
|
||||||
|
typeof message === 'string'
|
||||||
|
? err.message
|
||||||
|
: (JSON.stringify(err.message) ?? err.detail),
|
||||||
annotations: [],
|
annotations: [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { lastValueFrom, Observable } from 'rxjs'
|
import { lastValueFrom, Observable } from 'rxjs'
|
||||||
import { requestInference } from './sse'
|
import { requestInference } from './sse'
|
||||||
|
|
||||||
import { ReadableStream } from 'stream/web';
|
import { ReadableStream } from 'stream/web'
|
||||||
describe('requestInference', () => {
|
describe('requestInference', () => {
|
||||||
it('should send a request to the inference server and return an Observable', () => {
|
it('should send a request to the inference server and return an Observable', () => {
|
||||||
// Mock the fetch function
|
// Mock the fetch function
|
||||||
const mockFetch: any = jest.fn(() =>
|
const mockFetch: any = jest.fn(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({ choices: [{ message: { content: 'Generated response' } }] }),
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
choices: [{ message: { content: 'Generated response' } }],
|
||||||
|
}),
|
||||||
headers: new Headers(),
|
headers: new Headers(),
|
||||||
redirected: false,
|
redirected: false,
|
||||||
status: 200,
|
status: 200,
|
||||||
@ -36,7 +39,10 @@ describe('requestInference', () => {
|
|||||||
const mockFetch: any = jest.fn(() =>
|
const mockFetch: any = jest.fn(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
ok: false,
|
ok: false,
|
||||||
json: () => Promise.resolve({ error: { message: 'Wrong API Key', code: 'invalid_api_key' } }),
|
json: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
error: { message: 'Invalid API Key.', code: 'invalid_api_key' },
|
||||||
|
}),
|
||||||
headers: new Headers(),
|
headers: new Headers(),
|
||||||
redirected: false,
|
redirected: false,
|
||||||
status: 401,
|
status: 401,
|
||||||
@ -56,69 +62,85 @@ describe('requestInference', () => {
|
|||||||
|
|
||||||
// Assert the expected behavior
|
// Assert the expected behavior
|
||||||
expect(result).toBeInstanceOf(Observable)
|
expect(result).toBeInstanceOf(Observable)
|
||||||
expect(lastValueFrom(result)).rejects.toEqual({ message: 'Wrong API Key', code: 'invalid_api_key' })
|
expect(lastValueFrom(result)).rejects.toEqual({
|
||||||
|
message: 'Invalid API Key.',
|
||||||
|
code: 'invalid_api_key',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should handle a successful response with a transformResponse function', () => {
|
it('should handle a successful response with a transformResponse function', () => {
|
||||||
// Mock the fetch function
|
// Mock the fetch function
|
||||||
const mockFetch: any = jest.fn(() =>
|
const mockFetch: any = jest.fn(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => Promise.resolve({ choices: [{ message: { content: 'Generated response' } }] }),
|
json: () =>
|
||||||
headers: new Headers(),
|
Promise.resolve({
|
||||||
redirected: false,
|
choices: [{ message: { content: 'Generated response' } }],
|
||||||
status: 200,
|
|
||||||
statusText: 'OK',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest.spyOn(global, 'fetch').mockImplementation(mockFetch)
|
|
||||||
|
|
||||||
// Define the test inputs
|
|
||||||
const inferenceUrl = 'https://inference-server.com'
|
|
||||||
const requestBody = { message: 'Hello' }
|
|
||||||
const model = { id: 'model-id', parameters: { stream: false } }
|
|
||||||
const transformResponse = (data: any) => data.choices[0].message.content.toUpperCase()
|
|
||||||
|
|
||||||
// Call the function
|
|
||||||
const result = requestInference(inferenceUrl, requestBody, model, undefined, undefined, transformResponse)
|
|
||||||
|
|
||||||
// Assert the expected behavior
|
|
||||||
expect(result).toBeInstanceOf(Observable)
|
|
||||||
expect(lastValueFrom(result)).resolves.toEqual('GENERATED RESPONSE')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
it('should handle a successful response with streaming enabled', () => {
|
|
||||||
// Mock the fetch function
|
|
||||||
const mockFetch: any = jest.fn(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
ok: true,
|
|
||||||
body: new ReadableStream({
|
|
||||||
start(controller) {
|
|
||||||
controller.enqueue(new TextEncoder().encode('data: {"choices": [{"delta": {"content": "Streamed"}}]}'));
|
|
||||||
controller.enqueue(new TextEncoder().encode('data: [DONE]'));
|
|
||||||
controller.close();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
headers: new Headers(),
|
headers: new Headers(),
|
||||||
redirected: false,
|
redirected: false,
|
||||||
status: 200,
|
status: 200,
|
||||||
statusText: 'OK',
|
statusText: 'OK',
|
||||||
})
|
})
|
||||||
);
|
)
|
||||||
jest.spyOn(global, 'fetch').mockImplementation(mockFetch);
|
jest.spyOn(global, 'fetch').mockImplementation(mockFetch)
|
||||||
|
|
||||||
// Define the test inputs
|
|
||||||
const inferenceUrl = 'https://inference-server.com';
|
|
||||||
const requestBody = { message: 'Hello' };
|
|
||||||
const model = { id: 'model-id', parameters: { stream: true } };
|
|
||||||
|
|
||||||
// Call the function
|
|
||||||
const result = requestInference(inferenceUrl, requestBody, model);
|
|
||||||
|
|
||||||
// Assert the expected behavior
|
|
||||||
expect(result).toBeInstanceOf(Observable);
|
|
||||||
expect(lastValueFrom(result)).resolves.toEqual('Streamed');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Define the test inputs
|
||||||
|
const inferenceUrl = 'https://inference-server.com'
|
||||||
|
const requestBody = { message: 'Hello' }
|
||||||
|
const model = { id: 'model-id', parameters: { stream: false } }
|
||||||
|
const transformResponse = (data: any) =>
|
||||||
|
data.choices[0].message.content.toUpperCase()
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
const result = requestInference(
|
||||||
|
inferenceUrl,
|
||||||
|
requestBody,
|
||||||
|
model,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
transformResponse
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assert the expected behavior
|
||||||
|
expect(result).toBeInstanceOf(Observable)
|
||||||
|
expect(lastValueFrom(result)).resolves.toEqual('GENERATED RESPONSE')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle a successful response with streaming enabled', () => {
|
||||||
|
// Mock the fetch function
|
||||||
|
const mockFetch: any = jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
body: new ReadableStream({
|
||||||
|
start(controller) {
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
'data: {"choices": [{"delta": {"content": "Streamed"}}]}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
controller.enqueue(new TextEncoder().encode('data: [DONE]'))
|
||||||
|
controller.close()
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
headers: new Headers(),
|
||||||
|
redirected: false,
|
||||||
|
status: 200,
|
||||||
|
statusText: 'OK',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
jest.spyOn(global, 'fetch').mockImplementation(mockFetch)
|
||||||
|
|
||||||
|
// Define the test inputs
|
||||||
|
const inferenceUrl = 'https://inference-server.com'
|
||||||
|
const requestBody = { message: 'Hello' }
|
||||||
|
const model = { id: 'model-id', parameters: { stream: true } }
|
||||||
|
|
||||||
|
// Call the function
|
||||||
|
const result = requestInference(inferenceUrl, requestBody, model)
|
||||||
|
|
||||||
|
// Assert the expected behavior
|
||||||
|
expect(result).toBeInstanceOf(Observable)
|
||||||
|
expect(lastValueFrom(result)).resolves.toEqual('Streamed')
|
||||||
|
})
|
||||||
|
|||||||
@ -32,20 +32,19 @@ export function requestInference(
|
|||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json()
|
if (response.status === 401) {
|
||||||
let errorCode = ErrorCode.Unknown
|
throw {
|
||||||
if (data.error) {
|
code: ErrorCode.InvalidApiKey,
|
||||||
errorCode = data.error.code ?? data.error.type ?? ErrorCode.Unknown
|
message: 'Invalid API Key.',
|
||||||
} else if (response.status === 401) {
|
}
|
||||||
errorCode = ErrorCode.InvalidApiKey
|
|
||||||
}
|
}
|
||||||
const error = {
|
let data = await response.json()
|
||||||
message: data.error?.message ?? data.message ?? 'Error occurred.',
|
try {
|
||||||
code: errorCode,
|
handleError(data)
|
||||||
|
} catch (err) {
|
||||||
|
subscriber.error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
subscriber.error(error)
|
|
||||||
subscriber.complete()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
// There could be overriden stream parameter in the model
|
// There could be overriden stream parameter in the model
|
||||||
// that is set in request body (transformed payload)
|
// that is set in request body (transformed payload)
|
||||||
@ -54,9 +53,10 @@ export function requestInference(
|
|||||||
model.parameters?.stream === false
|
model.parameters?.stream === false
|
||||||
) {
|
) {
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
if (data.error || data.message) {
|
try {
|
||||||
subscriber.error(data.error ?? data)
|
handleError(data)
|
||||||
subscriber.complete()
|
} catch (err) {
|
||||||
|
subscriber.error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (transformResponse) {
|
if (transformResponse) {
|
||||||
@ -91,13 +91,10 @@ export function requestInference(
|
|||||||
const toParse = cachedLines + line
|
const toParse = cachedLines + line
|
||||||
if (!line.includes('data: [DONE]')) {
|
if (!line.includes('data: [DONE]')) {
|
||||||
const data = JSON.parse(toParse.replace('data: ', ''))
|
const data = JSON.parse(toParse.replace('data: ', ''))
|
||||||
if (
|
try {
|
||||||
'error' in data ||
|
handleError(data)
|
||||||
'message' in data ||
|
} catch (err) {
|
||||||
'detail' in data
|
subscriber.error(err)
|
||||||
) {
|
|
||||||
subscriber.error(data.error ?? data)
|
|
||||||
subscriber.complete()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
content += data.choices[0]?.delta?.content ?? ''
|
content += data.choices[0]?.delta?.content ?? ''
|
||||||
@ -118,3 +115,18 @@ export function requestInference(
|
|||||||
.catch((err) => subscriber.error(err))
|
.catch((err) => subscriber.error(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle error and normalize it to a common format.
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
const handleError = (data: any) => {
|
||||||
|
if (
|
||||||
|
data.error ||
|
||||||
|
data.message ||
|
||||||
|
data.detail ||
|
||||||
|
(Array.isArray(data) && data.length && data[0].error)
|
||||||
|
) {
|
||||||
|
throw data.error ?? data[0]?.error ?? data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
566
core/src/browser/extensions/enginesManagement.test.ts
Normal file
566
core/src/browser/extensions/enginesManagement.test.ts
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
import { EngineManagementExtension } from './enginesManagement'
|
||||||
|
import { ExtensionTypeEnum } from '../extension'
|
||||||
|
import {
|
||||||
|
EngineConfig,
|
||||||
|
EngineReleased,
|
||||||
|
EngineVariant,
|
||||||
|
Engines,
|
||||||
|
InferenceEngine,
|
||||||
|
DefaultEngineVariant,
|
||||||
|
Model
|
||||||
|
} from '../../types'
|
||||||
|
|
||||||
|
// Mock implementation of EngineManagementExtension
|
||||||
|
class MockEngineManagementExtension extends EngineManagementExtension {
|
||||||
|
private mockEngines: Engines = {
|
||||||
|
llama: {
|
||||||
|
name: 'llama',
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '1.0.0',
|
||||||
|
path: '/engines/llama/cpu/1.0.0',
|
||||||
|
installed: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'cuda',
|
||||||
|
version: '1.0.0',
|
||||||
|
path: '/engines/llama/cuda/1.0.0',
|
||||||
|
installed: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: {
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
gpt4all: {
|
||||||
|
name: 'gpt4all',
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '2.0.0',
|
||||||
|
path: '/engines/gpt4all/cpu/2.0.0',
|
||||||
|
installed: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
default: {
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '2.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mockReleases: { [key: string]: EngineReleased[] } = {
|
||||||
|
'llama-1.0.0': [
|
||||||
|
{
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '1.0.0',
|
||||||
|
os: ['macos', 'linux', 'windows'],
|
||||||
|
url: 'https://example.com/llama/1.0.0/cpu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'cuda',
|
||||||
|
version: '1.0.0',
|
||||||
|
os: ['linux', 'windows'],
|
||||||
|
url: 'https://example.com/llama/1.0.0/cuda'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'llama-1.1.0': [
|
||||||
|
{
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '1.1.0',
|
||||||
|
os: ['macos', 'linux', 'windows'],
|
||||||
|
url: 'https://example.com/llama/1.1.0/cpu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'cuda',
|
||||||
|
version: '1.1.0',
|
||||||
|
os: ['linux', 'windows'],
|
||||||
|
url: 'https://example.com/llama/1.1.0/cuda'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'gpt4all-2.0.0': [
|
||||||
|
{
|
||||||
|
variant: 'cpu',
|
||||||
|
version: '2.0.0',
|
||||||
|
os: ['macos', 'linux', 'windows'],
|
||||||
|
url: 'https://example.com/gpt4all/2.0.0/cpu'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private remoteModels: { [engine: string]: Model[] } = {
|
||||||
|
'llama': [],
|
||||||
|
'gpt4all': []
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('http://mock-url.com', 'mock-engine-extension', 'Mock Engine Extension', true, 'A mock engine extension', '1.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnload(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEngines(): Promise<Engines> {
|
||||||
|
return JSON.parse(JSON.stringify(this.mockEngines))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInstalledEngines(name: InferenceEngine): Promise<EngineVariant[]> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mockEngines[name].variants.filter(variant => variant.installed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getReleasedEnginesByVersion(
|
||||||
|
name: InferenceEngine,
|
||||||
|
version: string,
|
||||||
|
platform?: string
|
||||||
|
): Promise<EngineReleased[]> {
|
||||||
|
const key = `${name}-${version}`
|
||||||
|
let releases = this.mockReleases[key] || []
|
||||||
|
|
||||||
|
if (platform) {
|
||||||
|
releases = releases.filter(release => release.os.includes(platform))
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLatestReleasedEngine(
|
||||||
|
name: InferenceEngine,
|
||||||
|
platform?: string
|
||||||
|
): Promise<EngineReleased[]> {
|
||||||
|
// For mock, let's assume latest versions are 1.1.0 for llama and 2.0.0 for gpt4all
|
||||||
|
const latestVersions = {
|
||||||
|
'llama': '1.1.0',
|
||||||
|
'gpt4all': '2.0.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!latestVersions[name]) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getReleasedEnginesByVersion(name, latestVersions[name], platform)
|
||||||
|
}
|
||||||
|
|
||||||
|
async installEngine(
|
||||||
|
name: string,
|
||||||
|
engineConfig: EngineConfig
|
||||||
|
): Promise<{ messages: string }> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
this.mockEngines[name] = {
|
||||||
|
name,
|
||||||
|
variants: [],
|
||||||
|
default: {
|
||||||
|
variant: engineConfig.variant,
|
||||||
|
version: engineConfig.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if variant already exists
|
||||||
|
const existingVariantIndex = this.mockEngines[name].variants.findIndex(
|
||||||
|
v => v.variant === engineConfig.variant && v.version === engineConfig.version
|
||||||
|
)
|
||||||
|
|
||||||
|
if (existingVariantIndex >= 0) {
|
||||||
|
this.mockEngines[name].variants[existingVariantIndex].installed = true
|
||||||
|
} else {
|
||||||
|
this.mockEngines[name].variants.push({
|
||||||
|
variant: engineConfig.variant,
|
||||||
|
version: engineConfig.version,
|
||||||
|
path: `/engines/${name}/${engineConfig.variant}/${engineConfig.version}`,
|
||||||
|
installed: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: `Successfully installed ${name} ${engineConfig.variant} ${engineConfig.version}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
async addRemoteEngine(
|
||||||
|
engineConfig: EngineConfig
|
||||||
|
): Promise<{ messages: string }> {
|
||||||
|
const name = engineConfig.name || 'remote-engine'
|
||||||
|
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
this.mockEngines[name] = {
|
||||||
|
name,
|
||||||
|
variants: [],
|
||||||
|
default: {
|
||||||
|
variant: engineConfig.variant,
|
||||||
|
version: engineConfig.version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mockEngines[name].variants.push({
|
||||||
|
variant: engineConfig.variant,
|
||||||
|
version: engineConfig.version,
|
||||||
|
path: engineConfig.path || `/engines/${name}/${engineConfig.variant}/${engineConfig.version}`,
|
||||||
|
installed: true,
|
||||||
|
url: engineConfig.url
|
||||||
|
})
|
||||||
|
|
||||||
|
return { messages: `Successfully added remote engine ${name}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstallEngine(
|
||||||
|
name: InferenceEngine,
|
||||||
|
engineConfig: EngineConfig
|
||||||
|
): Promise<{ messages: string }> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
return { messages: `Engine ${name} not found` }
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantIndex = this.mockEngines[name].variants.findIndex(
|
||||||
|
v => v.variant === engineConfig.variant && v.version === engineConfig.version
|
||||||
|
)
|
||||||
|
|
||||||
|
if (variantIndex >= 0) {
|
||||||
|
this.mockEngines[name].variants[variantIndex].installed = false
|
||||||
|
|
||||||
|
// If this was the default variant, reset default
|
||||||
|
if (
|
||||||
|
this.mockEngines[name].default.variant === engineConfig.variant &&
|
||||||
|
this.mockEngines[name].default.version === engineConfig.version
|
||||||
|
) {
|
||||||
|
// Find another installed variant to set as default
|
||||||
|
const installedVariant = this.mockEngines[name].variants.find(v => v.installed)
|
||||||
|
if (installedVariant) {
|
||||||
|
this.mockEngines[name].default = {
|
||||||
|
variant: installedVariant.variant,
|
||||||
|
version: installedVariant.version
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No installed variants remain, clear default
|
||||||
|
this.mockEngines[name].default = { variant: '', version: '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: `Successfully uninstalled ${name} ${engineConfig.variant} ${engineConfig.version}` }
|
||||||
|
} else {
|
||||||
|
return { messages: `Variant ${engineConfig.variant} ${engineConfig.version} not found for engine ${name}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDefaultEngineVariant(
|
||||||
|
name: InferenceEngine
|
||||||
|
): Promise<DefaultEngineVariant> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
return { variant: '', version: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.mockEngines[name].default
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDefaultEngineVariant(
|
||||||
|
name: InferenceEngine,
|
||||||
|
engineConfig: EngineConfig
|
||||||
|
): Promise<{ messages: string }> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
return { messages: `Engine ${name} not found` }
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantExists = this.mockEngines[name].variants.some(
|
||||||
|
v => v.variant === engineConfig.variant && v.version === engineConfig.version && v.installed
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!variantExists) {
|
||||||
|
return { messages: `Variant ${engineConfig.variant} ${engineConfig.version} not found or not installed` }
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mockEngines[name].default = {
|
||||||
|
variant: engineConfig.variant,
|
||||||
|
version: engineConfig.version
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: `Successfully set ${engineConfig.variant} ${engineConfig.version} as default for ${name}` }
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateEngine(
|
||||||
|
name: InferenceEngine,
|
||||||
|
engineConfig?: EngineConfig
|
||||||
|
): Promise<{ messages: string }> {
|
||||||
|
if (!this.mockEngines[name]) {
|
||||||
|
return { messages: `Engine ${name} not found` }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!engineConfig) {
|
||||||
|
// Assume we're updating to the latest version
|
||||||
|
return { messages: `Successfully updated ${name} to the latest version` }
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantIndex = this.mockEngines[name].variants.findIndex(
|
||||||
|
v => v.variant === engineConfig.variant && v.installed
|
||||||
|
)
|
||||||
|
|
||||||
|
if (variantIndex >= 0) {
|
||||||
|
// Update the version
|
||||||
|
this.mockEngines[name].variants[variantIndex].version = engineConfig.version
|
||||||
|
|
||||||
|
// If this was the default variant, update default version too
|
||||||
|
if (this.mockEngines[name].default.variant === engineConfig.variant) {
|
||||||
|
this.mockEngines[name].default.version = engineConfig.version
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: `Successfully updated ${name} ${engineConfig.variant} to version ${engineConfig.version}` }
|
||||||
|
} else {
|
||||||
|
return { messages: `Installed variant ${engineConfig.variant} not found for engine ${name}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addRemoteModel(model: Model): Promise<void> {
|
||||||
|
const engine = model.engine as string
|
||||||
|
|
||||||
|
if (!this.remoteModels[engine]) {
|
||||||
|
this.remoteModels[engine] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.remoteModels[engine].push(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRemoteModels(name: InferenceEngine | string): Promise<Model[]> {
|
||||||
|
return this.remoteModels[name] || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('EngineManagementExtension', () => {
|
||||||
|
let extension: MockEngineManagementExtension
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
extension = new MockEngineManagementExtension()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return the correct extension type', () => {
|
||||||
|
expect(extension.type()).toBe(ExtensionTypeEnum.Engine)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get all engines', async () => {
|
||||||
|
const engines = await extension.getEngines()
|
||||||
|
|
||||||
|
expect(engines).toBeDefined()
|
||||||
|
expect(engines.llama).toBeDefined()
|
||||||
|
expect(engines.gpt4all).toBeDefined()
|
||||||
|
expect(engines.llama.variants).toHaveLength(2)
|
||||||
|
expect(engines.gpt4all.variants).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get installed engines', async () => {
|
||||||
|
const llamaEngines = await extension.getInstalledEngines('llama')
|
||||||
|
|
||||||
|
expect(llamaEngines).toHaveLength(1)
|
||||||
|
expect(llamaEngines[0].variant).toBe('cpu')
|
||||||
|
expect(llamaEngines[0].installed).toBe(true)
|
||||||
|
|
||||||
|
const gpt4allEngines = await extension.getInstalledEngines('gpt4all')
|
||||||
|
|
||||||
|
expect(gpt4allEngines).toHaveLength(1)
|
||||||
|
expect(gpt4allEngines[0].variant).toBe('cpu')
|
||||||
|
expect(gpt4allEngines[0].installed).toBe(true)
|
||||||
|
|
||||||
|
// Test non-existent engine
|
||||||
|
const nonExistentEngines = await extension.getInstalledEngines('non-existent' as InferenceEngine)
|
||||||
|
expect(nonExistentEngines).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get released engines by version', async () => {
|
||||||
|
const llamaReleases = await extension.getReleasedEnginesByVersion('llama', '1.0.0')
|
||||||
|
|
||||||
|
expect(llamaReleases).toHaveLength(2)
|
||||||
|
expect(llamaReleases[0].variant).toBe('cpu')
|
||||||
|
expect(llamaReleases[1].variant).toBe('cuda')
|
||||||
|
|
||||||
|
// Test with platform filter
|
||||||
|
const llamaLinuxReleases = await extension.getReleasedEnginesByVersion('llama', '1.0.0', 'linux')
|
||||||
|
|
||||||
|
expect(llamaLinuxReleases).toHaveLength(2)
|
||||||
|
|
||||||
|
const llamaMacReleases = await extension.getReleasedEnginesByVersion('llama', '1.0.0', 'macos')
|
||||||
|
|
||||||
|
expect(llamaMacReleases).toHaveLength(1)
|
||||||
|
expect(llamaMacReleases[0].variant).toBe('cpu')
|
||||||
|
|
||||||
|
// Test non-existent version
|
||||||
|
const nonExistentReleases = await extension.getReleasedEnginesByVersion('llama', '9.9.9')
|
||||||
|
expect(nonExistentReleases).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get latest released engines', async () => {
|
||||||
|
const latestLlamaReleases = await extension.getLatestReleasedEngine('llama')
|
||||||
|
|
||||||
|
expect(latestLlamaReleases).toHaveLength(2)
|
||||||
|
expect(latestLlamaReleases[0].version).toBe('1.1.0')
|
||||||
|
|
||||||
|
// Test with platform filter
|
||||||
|
const latestLlamaMacReleases = await extension.getLatestReleasedEngine('llama', 'macos')
|
||||||
|
|
||||||
|
expect(latestLlamaMacReleases).toHaveLength(1)
|
||||||
|
expect(latestLlamaMacReleases[0].variant).toBe('cpu')
|
||||||
|
expect(latestLlamaMacReleases[0].version).toBe('1.1.0')
|
||||||
|
|
||||||
|
// Test non-existent engine
|
||||||
|
const nonExistentReleases = await extension.getLatestReleasedEngine('non-existent' as InferenceEngine)
|
||||||
|
expect(nonExistentReleases).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should install engine', async () => {
|
||||||
|
// Install existing engine variant that is not installed
|
||||||
|
const result = await extension.installEngine('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(result.messages).toContain('Successfully installed')
|
||||||
|
|
||||||
|
const installedEngines = await extension.getInstalledEngines('llama')
|
||||||
|
expect(installedEngines).toHaveLength(2)
|
||||||
|
expect(installedEngines.some(e => e.variant === 'cuda')).toBe(true)
|
||||||
|
|
||||||
|
// Install non-existent engine
|
||||||
|
const newEngineResult = await extension.installEngine('new-engine', { variant: 'cpu', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(newEngineResult.messages).toContain('Successfully installed')
|
||||||
|
|
||||||
|
const engines = await extension.getEngines()
|
||||||
|
expect(engines['new-engine']).toBeDefined()
|
||||||
|
expect(engines['new-engine'].variants).toHaveLength(1)
|
||||||
|
expect(engines['new-engine'].variants[0].installed).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should add remote engine', async () => {
|
||||||
|
const result = await extension.addRemoteEngine({
|
||||||
|
name: 'remote-llm',
|
||||||
|
variant: 'remote',
|
||||||
|
version: '1.0.0',
|
||||||
|
url: 'https://example.com/remote-llm-api'
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result.messages).toContain('Successfully added remote engine')
|
||||||
|
|
||||||
|
const engines = await extension.getEngines()
|
||||||
|
expect(engines['remote-llm']).toBeDefined()
|
||||||
|
expect(engines['remote-llm'].variants).toHaveLength(1)
|
||||||
|
expect(engines['remote-llm'].variants[0].url).toBe('https://example.com/remote-llm-api')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should uninstall engine', async () => {
|
||||||
|
const result = await extension.uninstallEngine('llama', { variant: 'cpu', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(result.messages).toContain('Successfully uninstalled')
|
||||||
|
|
||||||
|
const installedEngines = await extension.getInstalledEngines('llama')
|
||||||
|
expect(installedEngines).toHaveLength(0)
|
||||||
|
|
||||||
|
// Test uninstalling non-existent variant
|
||||||
|
const nonExistentResult = await extension.uninstallEngine('llama', { variant: 'non-existent', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(nonExistentResult.messages).toContain('not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle default variant when uninstalling', async () => {
|
||||||
|
// First install cuda variant
|
||||||
|
await extension.installEngine('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
// Set cuda as default
|
||||||
|
await extension.setDefaultEngineVariant('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
// Check that cuda is now default
|
||||||
|
let defaultVariant = await extension.getDefaultEngineVariant('llama')
|
||||||
|
expect(defaultVariant.variant).toBe('cuda')
|
||||||
|
|
||||||
|
// Uninstall cuda
|
||||||
|
await extension.uninstallEngine('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
// Check that default has changed to another installed variant
|
||||||
|
defaultVariant = await extension.getDefaultEngineVariant('llama')
|
||||||
|
expect(defaultVariant.variant).toBe('cpu')
|
||||||
|
|
||||||
|
// Uninstall all variants
|
||||||
|
await extension.uninstallEngine('llama', { variant: 'cpu', version: '1.0.0' })
|
||||||
|
|
||||||
|
// Check that default is now empty
|
||||||
|
defaultVariant = await extension.getDefaultEngineVariant('llama')
|
||||||
|
expect(defaultVariant.variant).toBe('')
|
||||||
|
expect(defaultVariant.version).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get default engine variant', async () => {
|
||||||
|
const llamaDefault = await extension.getDefaultEngineVariant('llama')
|
||||||
|
|
||||||
|
expect(llamaDefault.variant).toBe('cpu')
|
||||||
|
expect(llamaDefault.version).toBe('1.0.0')
|
||||||
|
|
||||||
|
// Test non-existent engine
|
||||||
|
const nonExistentDefault = await extension.getDefaultEngineVariant('non-existent' as InferenceEngine)
|
||||||
|
expect(nonExistentDefault.variant).toBe('')
|
||||||
|
expect(nonExistentDefault.version).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should set default engine variant', async () => {
|
||||||
|
// Install cuda variant
|
||||||
|
await extension.installEngine('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
const result = await extension.setDefaultEngineVariant('llama', { variant: 'cuda', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(result.messages).toContain('Successfully set')
|
||||||
|
|
||||||
|
const defaultVariant = await extension.getDefaultEngineVariant('llama')
|
||||||
|
expect(defaultVariant.variant).toBe('cuda')
|
||||||
|
expect(defaultVariant.version).toBe('1.0.0')
|
||||||
|
|
||||||
|
// Test setting non-existent variant as default
|
||||||
|
const nonExistentResult = await extension.setDefaultEngineVariant('llama', { variant: 'non-existent', version: '1.0.0' })
|
||||||
|
|
||||||
|
expect(nonExistentResult.messages).toContain('not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should update engine', async () => {
|
||||||
|
const result = await extension.updateEngine('llama', { variant: 'cpu', version: '1.1.0' })
|
||||||
|
|
||||||
|
expect(result.messages).toContain('Successfully updated')
|
||||||
|
|
||||||
|
const engines = await extension.getEngines()
|
||||||
|
const cpuVariant = engines.llama.variants.find(v => v.variant === 'cpu')
|
||||||
|
expect(cpuVariant).toBeDefined()
|
||||||
|
expect(cpuVariant?.version).toBe('1.1.0')
|
||||||
|
|
||||||
|
// Default should also be updated since cpu was default
|
||||||
|
expect(engines.llama.default.version).toBe('1.1.0')
|
||||||
|
|
||||||
|
// Test updating non-existent variant
|
||||||
|
const nonExistentResult = await extension.updateEngine('llama', { variant: 'non-existent', version: '1.1.0' })
|
||||||
|
|
||||||
|
expect(nonExistentResult.messages).toContain('not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should add and get remote models', async () => {
|
||||||
|
const model: Model = {
|
||||||
|
id: 'remote-model-1',
|
||||||
|
name: 'Remote Model 1',
|
||||||
|
path: '/path/to/remote-model',
|
||||||
|
engine: 'llama',
|
||||||
|
format: 'gguf',
|
||||||
|
modelFormat: 'gguf',
|
||||||
|
source: 'remote',
|
||||||
|
status: 'ready',
|
||||||
|
contextLength: 4096,
|
||||||
|
sizeInGB: 4,
|
||||||
|
created: new Date().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.addRemoteModel(model)
|
||||||
|
|
||||||
|
const llamaModels = await extension.getRemoteModels('llama')
|
||||||
|
expect(llamaModels).toHaveLength(1)
|
||||||
|
expect(llamaModels[0].id).toBe('remote-model-1')
|
||||||
|
|
||||||
|
// Test non-existent engine
|
||||||
|
const nonExistentModels = await extension.getRemoteModels('non-existent')
|
||||||
|
expect(nonExistentModels).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
146
core/src/browser/extensions/hardwareManagement.test.ts
Normal file
146
core/src/browser/extensions/hardwareManagement.test.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import { HardwareManagementExtension } from './hardwareManagement'
|
||||||
|
import { ExtensionTypeEnum } from '../extension'
|
||||||
|
import { HardwareInformation } from '../../types'
|
||||||
|
|
||||||
|
// Mock implementation of HardwareManagementExtension
|
||||||
|
class MockHardwareManagementExtension extends HardwareManagementExtension {
|
||||||
|
private activeGpus: number[] = [0]
|
||||||
|
private mockHardwareInfo: HardwareInformation = {
|
||||||
|
cpu: {
|
||||||
|
manufacturer: 'Mock CPU Manufacturer',
|
||||||
|
brand: 'Mock CPU',
|
||||||
|
cores: 8,
|
||||||
|
physicalCores: 4,
|
||||||
|
speed: 3.5,
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
total: 16 * 1024 * 1024 * 1024, // 16GB in bytes
|
||||||
|
free: 8 * 1024 * 1024 * 1024, // 8GB in bytes
|
||||||
|
},
|
||||||
|
gpus: [
|
||||||
|
{
|
||||||
|
id: 0,
|
||||||
|
vendor: 'Mock GPU Vendor',
|
||||||
|
model: 'Mock GPU Model 1',
|
||||||
|
memory: 8 * 1024 * 1024 * 1024, // 8GB in bytes
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
vendor: 'Mock GPU Vendor',
|
||||||
|
model: 'Mock GPU Model 2',
|
||||||
|
memory: 4 * 1024 * 1024 * 1024, // 4GB in bytes
|
||||||
|
}
|
||||||
|
],
|
||||||
|
active_gpus: [0],
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('http://mock-url.com', 'mock-hardware-extension', 'Mock Hardware Extension', true, 'A mock hardware extension', '1.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnload(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHardware(): Promise<HardwareInformation> {
|
||||||
|
// Return a copy to prevent test side effects
|
||||||
|
return JSON.parse(JSON.stringify(this.mockHardwareInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
async setAvtiveGpu(data: { gpus: number[] }): Promise<{
|
||||||
|
message: string
|
||||||
|
activated_gpus: number[]
|
||||||
|
}> {
|
||||||
|
// Validate GPUs exist
|
||||||
|
const validGpus = data.gpus.filter(gpuId =>
|
||||||
|
this.mockHardwareInfo.gpus.some(gpu => gpu.id === gpuId)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (validGpus.length === 0) {
|
||||||
|
throw new Error('No valid GPUs selected')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update active GPUs
|
||||||
|
this.activeGpus = validGpus
|
||||||
|
this.mockHardwareInfo.active_gpus = validGpus
|
||||||
|
|
||||||
|
return {
|
||||||
|
message: 'GPU activation successful',
|
||||||
|
activated_gpus: validGpus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('HardwareManagementExtension', () => {
|
||||||
|
let extension: MockHardwareManagementExtension
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
extension = new MockHardwareManagementExtension()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return the correct extension type', () => {
|
||||||
|
expect(extension.type()).toBe(ExtensionTypeEnum.Hardware)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should get hardware information', async () => {
|
||||||
|
const hardwareInfo = await extension.getHardware()
|
||||||
|
|
||||||
|
// Check CPU info
|
||||||
|
expect(hardwareInfo.cpu).toBeDefined()
|
||||||
|
expect(hardwareInfo.cpu.manufacturer).toBe('Mock CPU Manufacturer')
|
||||||
|
expect(hardwareInfo.cpu.cores).toBe(8)
|
||||||
|
|
||||||
|
// Check memory info
|
||||||
|
expect(hardwareInfo.memory).toBeDefined()
|
||||||
|
expect(hardwareInfo.memory.total).toBe(16 * 1024 * 1024 * 1024)
|
||||||
|
|
||||||
|
// Check GPU info
|
||||||
|
expect(hardwareInfo.gpus).toHaveLength(2)
|
||||||
|
expect(hardwareInfo.gpus[0].model).toBe('Mock GPU Model 1')
|
||||||
|
expect(hardwareInfo.gpus[1].model).toBe('Mock GPU Model 2')
|
||||||
|
|
||||||
|
// Check active GPUs
|
||||||
|
expect(hardwareInfo.active_gpus).toEqual([0])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should set active GPUs', async () => {
|
||||||
|
const result = await extension.setAvtiveGpu({ gpus: [1] })
|
||||||
|
|
||||||
|
expect(result.message).toBe('GPU activation successful')
|
||||||
|
expect(result.activated_gpus).toEqual([1])
|
||||||
|
|
||||||
|
// Verify the change in hardware info
|
||||||
|
const hardwareInfo = await extension.getHardware()
|
||||||
|
expect(hardwareInfo.active_gpus).toEqual([1])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should set multiple active GPUs', async () => {
|
||||||
|
const result = await extension.setAvtiveGpu({ gpus: [0, 1] })
|
||||||
|
|
||||||
|
expect(result.message).toBe('GPU activation successful')
|
||||||
|
expect(result.activated_gpus).toEqual([0, 1])
|
||||||
|
|
||||||
|
// Verify the change in hardware info
|
||||||
|
const hardwareInfo = await extension.getHardware()
|
||||||
|
expect(hardwareInfo.active_gpus).toEqual([0, 1])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error for invalid GPU ids', async () => {
|
||||||
|
await expect(extension.setAvtiveGpu({ gpus: [999] })).rejects.toThrow('No valid GPUs selected')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should handle mix of valid and invalid GPU ids', async () => {
|
||||||
|
const result = await extension.setAvtiveGpu({ gpus: [0, 999] })
|
||||||
|
|
||||||
|
// Should only activate valid GPUs
|
||||||
|
expect(result.activated_gpus).toEqual([0])
|
||||||
|
|
||||||
|
// Verify the change in hardware info
|
||||||
|
const hardwareInfo = await extension.getHardware()
|
||||||
|
expect(hardwareInfo.active_gpus).toEqual([0])
|
||||||
|
})
|
||||||
|
})
|
||||||
26
core/src/browser/extensions/hardwareManagement.ts
Normal file
26
core/src/browser/extensions/hardwareManagement.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { HardwareInformation } from '../../types'
|
||||||
|
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Engine management extension. Persists and retrieves engine management.
|
||||||
|
* @abstract
|
||||||
|
* @extends BaseExtension
|
||||||
|
*/
|
||||||
|
export abstract class HardwareManagementExtension extends BaseExtension {
|
||||||
|
type(): ExtensionTypeEnum | undefined {
|
||||||
|
return ExtensionTypeEnum.Hardware
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A Promise that resolves to an object of list hardware.
|
||||||
|
*/
|
||||||
|
abstract getHardware(): Promise<HardwareInformation>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns A Promise that resolves to an object of set active gpus.
|
||||||
|
*/
|
||||||
|
abstract setAvtiveGpu(data: { gpus: number[] }): Promise<{
|
||||||
|
message: string
|
||||||
|
activated_gpus: number[]
|
||||||
|
}>
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { ConversationalExtension } from './index';
|
import { ConversationalExtension } from './index';
|
||||||
import { InferenceExtension } from './index';
|
import { InferenceExtension } from './index';
|
||||||
import { MonitoringExtension } from './index';
|
|
||||||
import { AssistantExtension } from './index';
|
import { AssistantExtension } from './index';
|
||||||
import { ModelExtension } from './index';
|
import { ModelExtension } from './index';
|
||||||
import * as Engines from './index';
|
import * as Engines from './index';
|
||||||
@ -14,10 +13,6 @@ describe('index.ts exports', () => {
|
|||||||
expect(InferenceExtension).toBeDefined();
|
expect(InferenceExtension).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should export MonitoringExtension', () => {
|
|
||||||
expect(MonitoringExtension).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should export AssistantExtension', () => {
|
test('should export AssistantExtension', () => {
|
||||||
expect(AssistantExtension).toBeDefined();
|
expect(AssistantExtension).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -29,4 +24,4 @@ describe('index.ts exports', () => {
|
|||||||
test('should export Engines', () => {
|
test('should export Engines', () => {
|
||||||
expect(Engines).toBeDefined();
|
expect(Engines).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,10 +9,7 @@ export { ConversationalExtension } from './conversational'
|
|||||||
*/
|
*/
|
||||||
export { InferenceExtension } from './inference'
|
export { InferenceExtension } from './inference'
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitoring extension for system monitoring.
|
|
||||||
*/
|
|
||||||
export { MonitoringExtension } from './monitoring'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assistant extension for managing assistants.
|
* Assistant extension for managing assistants.
|
||||||
@ -33,3 +30,8 @@ export * from './engines'
|
|||||||
* Engines Management
|
* Engines Management
|
||||||
*/
|
*/
|
||||||
export * from './enginesManagement'
|
export * from './enginesManagement'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hardware Management
|
||||||
|
*/
|
||||||
|
export * from './hardwareManagement'
|
||||||
|
|||||||
286
core/src/browser/extensions/model.test.ts
Normal file
286
core/src/browser/extensions/model.test.ts
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import { ModelExtension } from './model'
|
||||||
|
import { ExtensionTypeEnum } from '../extension'
|
||||||
|
import { Model, OptionType, ModelSource } from '../../types'
|
||||||
|
|
||||||
|
// Mock implementation of ModelExtension
|
||||||
|
class MockModelExtension extends ModelExtension {
|
||||||
|
private models: Model[] = []
|
||||||
|
private sources: ModelSource[] = []
|
||||||
|
private loadedModels: Set<string> = new Set()
|
||||||
|
private modelsPulling: Set<string> = new Set()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('http://mock-url.com', 'mock-model-extension', 'Mock Model Extension', true, 'A mock model extension', '1.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnload(): void {
|
||||||
|
// Mock implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
async configurePullOptions(configs: { [key: string]: any }): Promise<any> {
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
async getModels(): Promise<Model[]> {
|
||||||
|
return this.models
|
||||||
|
}
|
||||||
|
|
||||||
|
async pullModel(model: string, id?: string, name?: string): Promise<void> {
|
||||||
|
const modelId = id || `model-${Date.now()}`
|
||||||
|
this.modelsPulling.add(modelId)
|
||||||
|
|
||||||
|
// Simulate model pull by adding it to the model list
|
||||||
|
const newModel: Model = {
|
||||||
|
id: modelId,
|
||||||
|
path: `/models/${model}`,
|
||||||
|
name: name || model,
|
||||||
|
source: 'mock-source',
|
||||||
|
modelFormat: 'mock-format',
|
||||||
|
engine: 'mock-engine',
|
||||||
|
format: 'mock-format',
|
||||||
|
status: 'ready',
|
||||||
|
contextLength: 2048,
|
||||||
|
sizeInGB: 2,
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
pullProgress: {
|
||||||
|
percent: 100,
|
||||||
|
transferred: 0,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.models.push(newModel)
|
||||||
|
this.loadedModels.add(modelId)
|
||||||
|
this.modelsPulling.delete(modelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelModelPull(modelId: string): Promise<void> {
|
||||||
|
this.modelsPulling.delete(modelId)
|
||||||
|
// Remove the model if it's in the pulling state
|
||||||
|
this.models = this.models.filter(m => m.id !== modelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async importModel(
|
||||||
|
model: string,
|
||||||
|
modelPath: string,
|
||||||
|
name?: string,
|
||||||
|
optionType?: OptionType
|
||||||
|
): Promise<void> {
|
||||||
|
const newModel: Model = {
|
||||||
|
id: `model-${Date.now()}`,
|
||||||
|
path: modelPath,
|
||||||
|
name: name || model,
|
||||||
|
source: 'local',
|
||||||
|
modelFormat: optionType?.format || 'mock-format',
|
||||||
|
engine: optionType?.engine || 'mock-engine',
|
||||||
|
format: optionType?.format || 'mock-format',
|
||||||
|
status: 'ready',
|
||||||
|
contextLength: optionType?.contextLength || 2048,
|
||||||
|
sizeInGB: 2,
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.models.push(newModel)
|
||||||
|
this.loadedModels.add(newModel.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateModel(modelInfo: Partial<Model>): Promise<Model> {
|
||||||
|
if (!modelInfo.id) throw new Error('Model ID is required')
|
||||||
|
|
||||||
|
const index = this.models.findIndex(m => m.id === modelInfo.id)
|
||||||
|
if (index === -1) throw new Error('Model not found')
|
||||||
|
|
||||||
|
this.models[index] = { ...this.models[index], ...modelInfo }
|
||||||
|
return this.models[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteModel(modelId: string): Promise<void> {
|
||||||
|
this.models = this.models.filter(m => m.id !== modelId)
|
||||||
|
this.loadedModels.delete(modelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async isModelLoaded(modelId: string): Promise<boolean> {
|
||||||
|
return this.loadedModels.has(modelId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSources(): Promise<ModelSource[]> {
|
||||||
|
return this.sources
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSource(source: string): Promise<void> {
|
||||||
|
const newSource: ModelSource = {
|
||||||
|
id: `source-${Date.now()}`,
|
||||||
|
url: source,
|
||||||
|
name: `Source ${this.sources.length + 1}`,
|
||||||
|
type: 'mock-type'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sources.push(newSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSource(sourceId: string): Promise<void> {
|
||||||
|
this.sources = this.sources.filter(s => s.id !== sourceId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ModelExtension', () => {
|
||||||
|
let extension: MockModelExtension
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
extension = new MockModelExtension()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return the correct extension type', () => {
|
||||||
|
expect(extension.type()).toBe(ExtensionTypeEnum.Model)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should configure pull options', async () => {
|
||||||
|
const configs = { apiKey: 'test-key', baseUrl: 'https://test-url.com' }
|
||||||
|
const result = await extension.configurePullOptions(configs)
|
||||||
|
expect(result).toEqual(configs)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should add and get models', async () => {
|
||||||
|
await extension.pullModel('test-model', 'test-id', 'Test Model')
|
||||||
|
|
||||||
|
const models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
expect(models[0].id).toBe('test-id')
|
||||||
|
expect(models[0].name).toBe('Test Model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should pull model with default id and name', async () => {
|
||||||
|
await extension.pullModel('test-model')
|
||||||
|
|
||||||
|
const models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
expect(models[0].name).toBe('test-model')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should cancel model pull', async () => {
|
||||||
|
await extension.pullModel('test-model', 'test-id')
|
||||||
|
|
||||||
|
// Verify model exists
|
||||||
|
let models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
|
||||||
|
// Cancel the pull
|
||||||
|
await extension.cancelModelPull('test-id')
|
||||||
|
|
||||||
|
// Verify model was removed
|
||||||
|
models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should import model', async () => {
|
||||||
|
const optionType: OptionType = {
|
||||||
|
engine: 'test-engine',
|
||||||
|
format: 'test-format',
|
||||||
|
contextLength: 4096
|
||||||
|
}
|
||||||
|
|
||||||
|
await extension.importModel('test-model', '/path/to/model', 'Imported Model', optionType)
|
||||||
|
|
||||||
|
const models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
expect(models[0].name).toBe('Imported Model')
|
||||||
|
expect(models[0].engine).toBe('test-engine')
|
||||||
|
expect(models[0].format).toBe('test-format')
|
||||||
|
expect(models[0].contextLength).toBe(4096)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should import model with default values', async () => {
|
||||||
|
await extension.importModel('test-model', '/path/to/model')
|
||||||
|
|
||||||
|
const models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
expect(models[0].name).toBe('test-model')
|
||||||
|
expect(models[0].engine).toBe('mock-engine')
|
||||||
|
expect(models[0].format).toBe('mock-format')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should update model', async () => {
|
||||||
|
await extension.pullModel('test-model', 'test-id', 'Test Model')
|
||||||
|
|
||||||
|
const updatedModel = await extension.updateModel({
|
||||||
|
id: 'test-id',
|
||||||
|
name: 'Updated Model',
|
||||||
|
contextLength: 8192
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(updatedModel.name).toBe('Updated Model')
|
||||||
|
expect(updatedModel.contextLength).toBe(8192)
|
||||||
|
|
||||||
|
// Verify changes persisted
|
||||||
|
const models = await extension.getModels()
|
||||||
|
expect(models[0].name).toBe('Updated Model')
|
||||||
|
expect(models[0].contextLength).toBe(8192)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when updating non-existent model', async () => {
|
||||||
|
await expect(extension.updateModel({
|
||||||
|
id: 'non-existent',
|
||||||
|
name: 'Updated Model'
|
||||||
|
})).rejects.toThrow('Model not found')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should throw error when updating model without ID', async () => {
|
||||||
|
await expect(extension.updateModel({
|
||||||
|
name: 'Updated Model'
|
||||||
|
})).rejects.toThrow('Model ID is required')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should delete model', async () => {
|
||||||
|
await extension.pullModel('test-model', 'test-id')
|
||||||
|
|
||||||
|
// Verify model exists
|
||||||
|
let models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(1)
|
||||||
|
|
||||||
|
// Delete the model
|
||||||
|
await extension.deleteModel('test-id')
|
||||||
|
|
||||||
|
// Verify model was removed
|
||||||
|
models = await extension.getModels()
|
||||||
|
expect(models).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should check if model is loaded', async () => {
|
||||||
|
await extension.pullModel('test-model', 'test-id')
|
||||||
|
|
||||||
|
// Check if model is loaded
|
||||||
|
const isLoaded = await extension.isModelLoaded('test-id')
|
||||||
|
expect(isLoaded).toBe(true)
|
||||||
|
|
||||||
|
// Check if non-existent model is loaded
|
||||||
|
const nonExistentLoaded = await extension.isModelLoaded('non-existent')
|
||||||
|
expect(nonExistentLoaded).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should add and get sources', async () => {
|
||||||
|
await extension.addSource('https://test-source.com')
|
||||||
|
|
||||||
|
const sources = await extension.getSources()
|
||||||
|
expect(sources).toHaveLength(1)
|
||||||
|
expect(sources[0].url).toBe('https://test-source.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should delete source', async () => {
|
||||||
|
await extension.addSource('https://test-source.com')
|
||||||
|
|
||||||
|
// Get the source ID
|
||||||
|
const sources = await extension.getSources()
|
||||||
|
const sourceId = sources[0].id
|
||||||
|
|
||||||
|
// Delete the source
|
||||||
|
await extension.deleteSource(sourceId)
|
||||||
|
|
||||||
|
// Verify source was removed
|
||||||
|
const updatedSources = await extension.getSources()
|
||||||
|
expect(updatedSources).toHaveLength(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -1,42 +0,0 @@
|
|||||||
|
|
||||||
import { ExtensionTypeEnum } from '../extension';
|
|
||||||
import { MonitoringExtension } from './monitoring';
|
|
||||||
|
|
||||||
it('should have the correct type', () => {
|
|
||||||
class TestMonitoringExtension extends MonitoringExtension {
|
|
||||||
getGpuSetting(): Promise<GpuSetting | undefined> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getResourcesInfo(): Promise<any> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getCurrentLoad(): Promise<any> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getOsInfo(): Promise<OperatingSystemInfo> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const monitoringExtension = new TestMonitoringExtension();
|
|
||||||
expect(monitoringExtension.type()).toBe(ExtensionTypeEnum.SystemMonitoring);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
it('should create an instance of MonitoringExtension', () => {
|
|
||||||
class TestMonitoringExtension extends MonitoringExtension {
|
|
||||||
getGpuSetting(): Promise<GpuSetting | undefined> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getResourcesInfo(): Promise<any> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getCurrentLoad(): Promise<any> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
getOsInfo(): Promise<OperatingSystemInfo> {
|
|
||||||
throw new Error('Method not implemented.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const monitoringExtension = new TestMonitoringExtension();
|
|
||||||
expect(monitoringExtension).toBeInstanceOf(MonitoringExtension);
|
|
||||||
});
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
|
||||||
import { GpuSetting, MonitoringInterface, OperatingSystemInfo } from '../../types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitoring extension for system monitoring.
|
|
||||||
* @extends BaseExtension
|
|
||||||
*/
|
|
||||||
export abstract class MonitoringExtension extends BaseExtension implements MonitoringInterface {
|
|
||||||
/**
|
|
||||||
* Monitoring extension type.
|
|
||||||
*/
|
|
||||||
type(): ExtensionTypeEnum | undefined {
|
|
||||||
return ExtensionTypeEnum.SystemMonitoring
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getGpuSetting(): Promise<GpuSetting | undefined>
|
|
||||||
abstract getResourcesInfo(): Promise<any>
|
|
||||||
abstract getCurrentLoad(): Promise<any>
|
|
||||||
abstract getOsInfo(): Promise<OperatingSystemInfo>
|
|
||||||
}
|
|
||||||
@ -55,17 +55,23 @@ const unlinkSync = (...args: any[]) => globalThis.core.api?.unlinkSync(...args)
|
|||||||
*/
|
*/
|
||||||
const appendFileSync = (...args: any[]) => globalThis.core.api?.appendFileSync(...args)
|
const appendFileSync = (...args: any[]) => globalThis.core.api?.appendFileSync(...args)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies a file from the source path to the destination path.
|
||||||
|
* @param src
|
||||||
|
* @param dest
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
const copyFile: (src: string, dest: string) => Promise<void> = (src, dest) =>
|
const copyFile: (src: string, dest: string) => Promise<void> = (src, dest) =>
|
||||||
globalThis.core.api?.copyFile(src, dest)
|
globalThis.core.api?.copyFile(src, dest)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the list of gguf files in a directory
|
* Gets the list of gguf files in a directory
|
||||||
*
|
*
|
||||||
* @param path - The paths to the file.
|
* @param path - The paths to the file.
|
||||||
* @returns {Promise<{any}>} - A promise that resolves with the list of gguf and non-gguf files
|
* @returns {Promise<{any}>} - A promise that resolves with the list of gguf and non-gguf files
|
||||||
*/
|
*/
|
||||||
const getGgufFiles: (paths: string[]) => Promise<any> = (
|
const getGgufFiles: (paths: string[]) => Promise<any> = (paths) =>
|
||||||
paths) => globalThis.core.api?.getGgufFiles(paths)
|
globalThis.core.api?.getGgufFiles(paths)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the file's stats.
|
* Gets the file's stats.
|
||||||
|
|||||||
@ -1,25 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
AppRoute,
|
AppRoute,
|
||||||
DownloadRoute,
|
|
||||||
ExtensionRoute,
|
ExtensionRoute,
|
||||||
FileManagerRoute,
|
FileManagerRoute,
|
||||||
FileSystemRoute,
|
FileSystemRoute,
|
||||||
} from '../../../types/api'
|
} from '../../../types/api'
|
||||||
import { Downloader } from '../processors/download'
|
|
||||||
import { FileSystem } from '../processors/fs'
|
import { FileSystem } from '../processors/fs'
|
||||||
import { Extension } from '../processors/extension'
|
import { Extension } from '../processors/extension'
|
||||||
import { FSExt } from '../processors/fsExt'
|
import { FSExt } from '../processors/fsExt'
|
||||||
import { App } from '../processors/app'
|
import { App } from '../processors/app'
|
||||||
|
|
||||||
export class RequestAdapter {
|
export class RequestAdapter {
|
||||||
downloader: Downloader
|
|
||||||
fileSystem: FileSystem
|
fileSystem: FileSystem
|
||||||
extension: Extension
|
extension: Extension
|
||||||
fsExt: FSExt
|
fsExt: FSExt
|
||||||
app: App
|
app: App
|
||||||
|
|
||||||
constructor(observer?: Function) {
|
constructor(observer?: Function) {
|
||||||
this.downloader = new Downloader(observer)
|
|
||||||
this.fileSystem = new FileSystem()
|
this.fileSystem = new FileSystem()
|
||||||
this.extension = new Extension()
|
this.extension = new Extension()
|
||||||
this.fsExt = new FSExt()
|
this.fsExt = new FSExt()
|
||||||
@ -28,9 +24,7 @@ export class RequestAdapter {
|
|||||||
|
|
||||||
// TODO: Clearer Factory pattern here
|
// TODO: Clearer Factory pattern here
|
||||||
process(route: string, ...args: any) {
|
process(route: string, ...args: any) {
|
||||||
if (route in DownloadRoute) {
|
if (route in FileSystemRoute) {
|
||||||
return this.downloader.process(route, ...args)
|
|
||||||
} else if (route in FileSystemRoute) {
|
|
||||||
return this.fileSystem.process(route, ...args)
|
return this.fileSystem.process(route, ...args)
|
||||||
} else if (route in ExtensionRoute) {
|
} else if (route in ExtensionRoute) {
|
||||||
return this.extension.process(route, ...args)
|
return this.extension.process(route, ...args)
|
||||||
|
|||||||
@ -1,125 +0,0 @@
|
|||||||
import { Downloader } from './download'
|
|
||||||
import { DownloadEvent } from '../../../types/api'
|
|
||||||
import { DownloadManager } from '../../helper/download'
|
|
||||||
|
|
||||||
jest.mock('../../helper', () => ({
|
|
||||||
getJanDataFolderPath: jest.fn().mockReturnValue('path/to/folder'),
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock('../../helper/path', () => ({
|
|
||||||
validatePath: jest.fn().mockReturnValue('path/to/folder'),
|
|
||||||
normalizeFilePath: () =>
|
|
||||||
process.platform === 'win32' ? 'C:\\Users\\path\\to\\file.gguf' : '/Users/path/to/file.gguf',
|
|
||||||
}))
|
|
||||||
|
|
||||||
jest.mock(
|
|
||||||
'request',
|
|
||||||
jest.fn().mockReturnValue(() => ({
|
|
||||||
on: jest.fn(),
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
|
|
||||||
jest.mock('fs', () => ({
|
|
||||||
createWriteStream: jest.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
const requestMock = jest.fn((options, callback) => {
|
|
||||||
callback(new Error('Test error'), null)
|
|
||||||
})
|
|
||||||
jest.mock('request', () => requestMock)
|
|
||||||
|
|
||||||
jest.mock('request-progress', () => {
|
|
||||||
return jest.fn().mockImplementation(() => {
|
|
||||||
return {
|
|
||||||
on: jest.fn().mockImplementation((event, callback) => {
|
|
||||||
if (event === 'error') {
|
|
||||||
callback(new Error('Download failed'))
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
on: jest.fn().mockImplementation((event, callback) => {
|
|
||||||
if (event === 'error') {
|
|
||||||
callback(new Error('Download failed'))
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
on: jest.fn().mockImplementation((event, callback) => {
|
|
||||||
if (event === 'error') {
|
|
||||||
callback(new Error('Download failed'))
|
|
||||||
}
|
|
||||||
return { pipe: jest.fn() }
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Downloader', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should pause download correctly', () => {
|
|
||||||
const observer = jest.fn()
|
|
||||||
const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'
|
|
||||||
|
|
||||||
const downloader = new Downloader(observer)
|
|
||||||
const pauseMock = jest.fn()
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = { pause: pauseMock }
|
|
||||||
|
|
||||||
downloader.pauseDownload(observer, fileName)
|
|
||||||
|
|
||||||
expect(pauseMock).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should resume download correctly', () => {
|
|
||||||
const observer = jest.fn()
|
|
||||||
const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'
|
|
||||||
|
|
||||||
const downloader = new Downloader(observer)
|
|
||||||
const resumeMock = jest.fn()
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = { resume: resumeMock }
|
|
||||||
|
|
||||||
downloader.resumeDownload(observer, fileName)
|
|
||||||
|
|
||||||
expect(resumeMock).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle aborting a download correctly', () => {
|
|
||||||
const observer = jest.fn()
|
|
||||||
const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file'
|
|
||||||
|
|
||||||
const downloader = new Downloader(observer)
|
|
||||||
const abortMock = jest.fn()
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = { abort: abortMock }
|
|
||||||
|
|
||||||
downloader.abortDownload(observer, fileName)
|
|
||||||
|
|
||||||
expect(abortMock).toHaveBeenCalled()
|
|
||||||
expect(observer).toHaveBeenCalledWith(
|
|
||||||
DownloadEvent.onFileDownloadError,
|
|
||||||
expect.objectContaining({
|
|
||||||
error: 'aborted',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle download fail correctly', () => {
|
|
||||||
const observer = jest.fn()
|
|
||||||
const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file.gguf'
|
|
||||||
|
|
||||||
const downloader = new Downloader(observer)
|
|
||||||
|
|
||||||
downloader.downloadFile(observer, {
|
|
||||||
localPath: fileName,
|
|
||||||
url: 'http://127.0.0.1',
|
|
||||||
})
|
|
||||||
expect(observer).toHaveBeenCalledWith(
|
|
||||||
DownloadEvent.onFileDownloadError,
|
|
||||||
expect.objectContaining({
|
|
||||||
error: expect.anything(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
import { resolve, sep } from 'path'
|
|
||||||
import { DownloadEvent } from '../../../types/api'
|
|
||||||
import { normalizeFilePath } from '../../helper/path'
|
|
||||||
import { getJanDataFolderPath } from '../../helper'
|
|
||||||
import { DownloadManager } from '../../helper/download'
|
|
||||||
import { createWriteStream, renameSync } from 'fs'
|
|
||||||
import { Processor } from './Processor'
|
|
||||||
import { DownloadRequest, DownloadState, NetworkConfig } from '../../../types'
|
|
||||||
|
|
||||||
export class Downloader implements Processor {
|
|
||||||
observer?: Function
|
|
||||||
|
|
||||||
constructor(observer?: Function) {
|
|
||||||
this.observer = observer
|
|
||||||
}
|
|
||||||
|
|
||||||
process(key: string, ...args: any[]): any {
|
|
||||||
const instance = this as any
|
|
||||||
const func = instance[key]
|
|
||||||
return func(this.observer, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadFile(observer: any, downloadRequest: DownloadRequest, network?: NetworkConfig) {
|
|
||||||
const request = require('request')
|
|
||||||
const progress = require('request-progress')
|
|
||||||
|
|
||||||
const strictSSL = !network?.ignoreSSL
|
|
||||||
const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined
|
|
||||||
|
|
||||||
const { localPath, url } = downloadRequest
|
|
||||||
let normalizedPath = localPath
|
|
||||||
if (typeof localPath === 'string') {
|
|
||||||
normalizedPath = normalizeFilePath(localPath)
|
|
||||||
}
|
|
||||||
const array = normalizedPath.split(sep)
|
|
||||||
const fileName = array.pop() ?? ''
|
|
||||||
const modelId = downloadRequest.modelId ?? array.pop() ?? ''
|
|
||||||
|
|
||||||
const destination = resolve(getJanDataFolderPath(), normalizedPath)
|
|
||||||
const rq = request({ url, strictSSL, proxy })
|
|
||||||
|
|
||||||
// Put request to download manager instance
|
|
||||||
DownloadManager.instance.setRequest(normalizedPath, rq)
|
|
||||||
|
|
||||||
// Downloading file to a temp file first
|
|
||||||
const downloadingTempFile = `${destination}.download`
|
|
||||||
|
|
||||||
// adding initial download state
|
|
||||||
const initialDownloadState: DownloadState = {
|
|
||||||
modelId,
|
|
||||||
fileName,
|
|
||||||
percent: 0,
|
|
||||||
size: {
|
|
||||||
total: 0,
|
|
||||||
transferred: 0,
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
downloadState: 'downloading',
|
|
||||||
extensionId: downloadRequest.extensionId,
|
|
||||||
downloadType: downloadRequest.downloadType,
|
|
||||||
localPath: normalizedPath,
|
|
||||||
}
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = initialDownloadState
|
|
||||||
DownloadManager.instance.downloadInfo[normalizedPath] = initialDownloadState
|
|
||||||
|
|
||||||
if (downloadRequest.downloadType === 'extension') {
|
|
||||||
observer?.(DownloadEvent.onFileDownloadUpdate, initialDownloadState)
|
|
||||||
}
|
|
||||||
|
|
||||||
progress(rq, {})
|
|
||||||
.on('progress', (state: any) => {
|
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
|
||||||
const downloadState: DownloadState = {
|
|
||||||
...currentDownloadState,
|
|
||||||
...state,
|
|
||||||
fileName: fileName,
|
|
||||||
downloadState: 'downloading',
|
|
||||||
}
|
|
||||||
console.debug('progress: ', downloadState)
|
|
||||||
observer?.(DownloadEvent.onFileDownloadUpdate, downloadState)
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
|
||||||
})
|
|
||||||
.on('error', (error: Error) => {
|
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
|
||||||
const downloadState: DownloadState = {
|
|
||||||
...currentDownloadState,
|
|
||||||
fileName: fileName,
|
|
||||||
error: error.message,
|
|
||||||
downloadState: 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
observer?.(DownloadEvent.onFileDownloadError, downloadState)
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
|
||||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
|
||||||
if (
|
|
||||||
currentDownloadState &&
|
|
||||||
DownloadManager.instance.networkRequests[normalizedPath] &&
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId]?.downloadState !== 'error'
|
|
||||||
) {
|
|
||||||
// Finished downloading, rename temp file to actual file
|
|
||||||
renameSync(downloadingTempFile, destination)
|
|
||||||
const downloadState: DownloadState = {
|
|
||||||
...currentDownloadState,
|
|
||||||
fileName: fileName,
|
|
||||||
downloadState: 'end',
|
|
||||||
}
|
|
||||||
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
|
|
||||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.pipe(createWriteStream(downloadingTempFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
abortDownload(observer: any, fileName: string) {
|
|
||||||
const rq = DownloadManager.instance.networkRequests[fileName]
|
|
||||||
if (rq) {
|
|
||||||
DownloadManager.instance.networkRequests[fileName] = undefined
|
|
||||||
rq?.abort()
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadInfo = DownloadManager.instance.downloadInfo[fileName]
|
|
||||||
observer?.(DownloadEvent.onFileDownloadError, {
|
|
||||||
...downloadInfo,
|
|
||||||
fileName,
|
|
||||||
error: 'aborted',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resumeDownload(_observer: any, fileName: any) {
|
|
||||||
DownloadManager.instance.networkRequests[fileName]?.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
pauseDownload(_observer: any, fileName: any) {
|
|
||||||
DownloadManager.instance.networkRequests[fileName]?.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { DownloadManager } from './download';
|
|
||||||
|
|
||||||
it('should set a network request for a specific file', () => {
|
|
||||||
const downloadManager = new DownloadManager();
|
|
||||||
const fileName = 'testFile';
|
|
||||||
const request = { url: 'http://example.com' };
|
|
||||||
|
|
||||||
downloadManager.setRequest(fileName, request);
|
|
||||||
|
|
||||||
expect(downloadManager.networkRequests[fileName]).toEqual(request);
|
|
||||||
});
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import { DownloadState } from '../../types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages file downloads and network requests.
|
|
||||||
*/
|
|
||||||
export class DownloadManager {
|
|
||||||
public networkRequests: Record<string, any> = {}
|
|
||||||
|
|
||||||
public static instance: DownloadManager = new DownloadManager()
|
|
||||||
|
|
||||||
// store the download information with key is model id
|
|
||||||
public downloadProgressMap: Record<string, DownloadState> = {}
|
|
||||||
|
|
||||||
// store the download information with key is normalized file path
|
|
||||||
public downloadInfo: Record<string, DownloadState> = {}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (DownloadManager.instance) {
|
|
||||||
return DownloadManager.instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets a network request for a specific file.
|
|
||||||
* @param {string} fileName - The name of the file.
|
|
||||||
* @param {Request | undefined} request - The network request to set, or undefined to clear the request.
|
|
||||||
*/
|
|
||||||
setRequest(fileName: string, request: any | undefined) {
|
|
||||||
this.networkRequests[fileName] = request
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export * from './config'
|
export * from './config'
|
||||||
export * from './download'
|
|
||||||
export * from './logger'
|
export * from './logger'
|
||||||
export * from './module'
|
export * from './module'
|
||||||
export * from './path'
|
export * from './path'
|
||||||
|
|||||||
@ -31,6 +31,10 @@ export enum NativeRoute {
|
|||||||
|
|
||||||
startServer = 'startServer',
|
startServer = 'startServer',
|
||||||
stopServer = 'stopServer',
|
stopServer = 'stopServer',
|
||||||
|
|
||||||
|
appUpdateDownload = 'appUpdateDownload',
|
||||||
|
|
||||||
|
appToken = 'appToken',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,6 +54,8 @@ export enum AppRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum AppEvent {
|
export enum AppEvent {
|
||||||
|
onAppUpdateNotAvailable = 'onAppUpdateNotAvailable',
|
||||||
|
onAppUpdateAvailable = 'onAppUpdateAvailable',
|
||||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||||
@ -61,30 +67,13 @@ export enum AppEvent {
|
|||||||
onMainViewStateChange = 'onMainViewStateChange',
|
onMainViewStateChange = 'onMainViewStateChange',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DownloadRoute {
|
|
||||||
abortDownload = 'abortDownload',
|
|
||||||
downloadFile = 'downloadFile',
|
|
||||||
pauseDownload = 'pauseDownload',
|
|
||||||
resumeDownload = 'resumeDownload',
|
|
||||||
getDownloadProgress = 'getDownloadProgress',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DownloadEvent {
|
export enum DownloadEvent {
|
||||||
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
||||||
onFileDownloadError = 'onFileDownloadError',
|
onFileDownloadError = 'onFileDownloadError',
|
||||||
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
||||||
onFileDownloadStopped = 'onFileDownloadStopped',
|
onFileDownloadStopped = 'onFileDownloadStopped',
|
||||||
onFileDownloadStarted = 'onFileDownloadStarted',
|
onFileDownloadStarted = 'onFileDownloadStarted',
|
||||||
onFileUnzipSuccess = 'onFileUnzipSuccess',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LocalImportModelEvent {
|
|
||||||
onLocalImportModelUpdate = 'onLocalImportModelUpdate',
|
|
||||||
onLocalImportModelFailed = 'onLocalImportModelFailed',
|
|
||||||
onLocalImportModelSuccess = 'onLocalImportModelSuccess',
|
|
||||||
onLocalImportModelFinished = 'onLocalImportModelFinished',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ExtensionRoute {
|
export enum ExtensionRoute {
|
||||||
baseExtensions = 'baseExtensions',
|
baseExtensions = 'baseExtensions',
|
||||||
getActiveExtensions = 'getActiveExtensions',
|
getActiveExtensions = 'getActiveExtensions',
|
||||||
@ -127,10 +116,6 @@ export type AppEventFunctions = {
|
|||||||
[K in AppEvent]: ApiFunction
|
[K in AppEvent]: ApiFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadRouteFunctions = {
|
|
||||||
[K in DownloadRoute]: ApiFunction
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DownloadEventFunctions = {
|
export type DownloadEventFunctions = {
|
||||||
[K in DownloadEvent]: ApiFunction
|
[K in DownloadEvent]: ApiFunction
|
||||||
}
|
}
|
||||||
@ -150,7 +135,6 @@ export type FileManagerRouteFunctions = {
|
|||||||
export type APIFunctions = NativeRouteFunctions &
|
export type APIFunctions = NativeRouteFunctions &
|
||||||
AppRouteFunctions &
|
AppRouteFunctions &
|
||||||
AppEventFunctions &
|
AppEventFunctions &
|
||||||
DownloadRouteFunctions &
|
|
||||||
DownloadEventFunctions &
|
DownloadEventFunctions &
|
||||||
ExtensionRouteFunctions &
|
ExtensionRouteFunctions &
|
||||||
FileSystemRouteFunctions &
|
FileSystemRouteFunctions &
|
||||||
@ -158,7 +142,6 @@ export type APIFunctions = NativeRouteFunctions &
|
|||||||
|
|
||||||
export const CoreRoutes = [
|
export const CoreRoutes = [
|
||||||
...Object.values(AppRoute),
|
...Object.values(AppRoute),
|
||||||
...Object.values(DownloadRoute),
|
|
||||||
...Object.values(ExtensionRoute),
|
...Object.values(ExtensionRoute),
|
||||||
...Object.values(FileSystemRoute),
|
...Object.values(FileSystemRoute),
|
||||||
...Object.values(FileManagerRoute),
|
...Object.values(FileManagerRoute),
|
||||||
@ -168,7 +151,6 @@ export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)]
|
|||||||
export const APIEvents = [
|
export const APIEvents = [
|
||||||
...Object.values(AppEvent),
|
...Object.values(AppEvent),
|
||||||
...Object.values(DownloadEvent),
|
...Object.values(DownloadEvent),
|
||||||
...Object.values(LocalImportModelEvent),
|
|
||||||
]
|
]
|
||||||
export type PayloadType = {
|
export type PayloadType = {
|
||||||
messages: ChatCompletionMessage[]
|
messages: ChatCompletionMessage[]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export type AppConfiguration = {
|
export type AppConfiguration = {
|
||||||
data_folder: string
|
data_folder: string
|
||||||
quick_ask: boolean
|
quick_ask: boolean
|
||||||
|
distinct_id?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ export type EngineMetadata = {
|
|||||||
template?: string
|
template?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
explore_models_url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EngineVariant = {
|
export type EngineVariant = {
|
||||||
|
|||||||
@ -16,41 +16,9 @@ export type DownloadState = {
|
|||||||
|
|
||||||
error?: string
|
error?: string
|
||||||
extensionId?: string
|
extensionId?: string
|
||||||
downloadType?: DownloadType | string
|
|
||||||
localPath?: string
|
localPath?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadType = 'model' | 'extension'
|
|
||||||
|
|
||||||
export type DownloadRequest = {
|
|
||||||
/**
|
|
||||||
* The URL to download the file from.
|
|
||||||
*/
|
|
||||||
url: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The local path to save the file to.
|
|
||||||
*/
|
|
||||||
localPath: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The extension ID of the extension that initiated the download.
|
|
||||||
*
|
|
||||||
* Can be extension name.
|
|
||||||
*/
|
|
||||||
extensionId?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The model ID of the model that initiated the download.
|
|
||||||
*/
|
|
||||||
modelId?: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The download type.
|
|
||||||
*/
|
|
||||||
downloadType?: DownloadType | string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloadTime = {
|
type DownloadTime = {
|
||||||
elapsed: number
|
elapsed: number
|
||||||
remaining: number
|
remaining: number
|
||||||
@ -60,7 +28,6 @@ type DownloadSize = {
|
|||||||
total: number
|
total: number
|
||||||
transferred: number
|
transferred: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The file metadata
|
* The file metadata
|
||||||
*/
|
*/
|
||||||
|
|||||||
55
core/src/types/hardware/index.ts
Normal file
55
core/src/types/hardware/index.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
export type Cpu = {
|
||||||
|
arch: string
|
||||||
|
cores: number
|
||||||
|
instructions: string[]
|
||||||
|
model: string
|
||||||
|
usage: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GpuAdditionalInformation = {
|
||||||
|
compute_cap: string
|
||||||
|
driver_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Gpu = {
|
||||||
|
activated: boolean
|
||||||
|
additional_information?: GpuAdditionalInformation
|
||||||
|
free_vram: number
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
total_vram: number
|
||||||
|
uuid: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Os = {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Power = {
|
||||||
|
battery_life: number
|
||||||
|
charging_status: string
|
||||||
|
is_power_saving: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Ram = {
|
||||||
|
available: number
|
||||||
|
total: number
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Storage = {
|
||||||
|
available: number
|
||||||
|
total: number
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HardwareInformation = {
|
||||||
|
cpu: Cpu
|
||||||
|
gpus: Gpu[]
|
||||||
|
os: Os
|
||||||
|
power: Power
|
||||||
|
ram: Ram
|
||||||
|
storage: Storage
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import * as model from './model';
|
|||||||
import * as thread from './thread';
|
import * as thread from './thread';
|
||||||
import * as message from './message';
|
import * as message from './message';
|
||||||
import * as inference from './inference';
|
import * as inference from './inference';
|
||||||
import * as monitoring from './monitoring';
|
|
||||||
import * as file from './file';
|
import * as file from './file';
|
||||||
import * as config from './config';
|
import * as config from './config';
|
||||||
import * as huggingface from './huggingface';
|
import * as huggingface from './huggingface';
|
||||||
@ -18,7 +17,6 @@ import * as setting from './setting';
|
|||||||
expect(thread).toBeDefined();
|
expect(thread).toBeDefined();
|
||||||
expect(message).toBeDefined();
|
expect(message).toBeDefined();
|
||||||
expect(inference).toBeDefined();
|
expect(inference).toBeDefined();
|
||||||
expect(monitoring).toBeDefined();
|
|
||||||
expect(file).toBeDefined();
|
expect(file).toBeDefined();
|
||||||
expect(config).toBeDefined();
|
expect(config).toBeDefined();
|
||||||
expect(huggingface).toBeDefined();
|
expect(huggingface).toBeDefined();
|
||||||
|
|||||||
@ -3,7 +3,6 @@ export * from './model'
|
|||||||
export * from './thread'
|
export * from './thread'
|
||||||
export * from './message'
|
export * from './message'
|
||||||
export * from './inference'
|
export * from './inference'
|
||||||
export * from './monitoring'
|
|
||||||
export * from './file'
|
export * from './file'
|
||||||
export * from './config'
|
export * from './config'
|
||||||
export * from './huggingface'
|
export * from './huggingface'
|
||||||
@ -11,3 +10,4 @@ export * from './miscellaneous'
|
|||||||
export * from './api'
|
export * from './api'
|
||||||
export * from './setting'
|
export * from './setting'
|
||||||
export * from './engine'
|
export * from './engine'
|
||||||
|
export * from './hardware'
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
export type FileDownloadRequest = {
|
|
||||||
downloadId: string
|
|
||||||
url: string
|
|
||||||
localPath: string
|
|
||||||
fileName: string
|
|
||||||
displayName: string
|
|
||||||
metadata: Record<string, string | number>
|
|
||||||
}
|
|
||||||
@ -1,6 +1,4 @@
|
|||||||
export * from './systemResourceInfo'
|
export * from './systemResourceInfo'
|
||||||
export * from './promptTemplate'
|
export * from './promptTemplate'
|
||||||
export * from './appUpdate'
|
export * from './appUpdate'
|
||||||
export * from './fileDownloadRequest'
|
|
||||||
export * from './networkConfig'
|
|
||||||
export * from './selectFiles'
|
export * from './selectFiles'
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
export type NetworkConfig = {
|
|
||||||
proxy?: string
|
|
||||||
ignoreSSL?: boolean
|
|
||||||
}
|
|
||||||
@ -1,33 +1,25 @@
|
|||||||
|
import { GpuAdditionalInformation } from '../hardware'
|
||||||
|
|
||||||
export type SystemResourceInfo = {
|
export type SystemResourceInfo = {
|
||||||
memAvailable: number
|
memAvailable: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RunMode = 'cpu' | 'gpu'
|
|
||||||
|
|
||||||
export type GpuSetting = {
|
export type GpuSetting = {
|
||||||
notify: boolean
|
|
||||||
run_mode: RunMode
|
|
||||||
nvidia_driver: {
|
|
||||||
exist: boolean
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
cuda: {
|
|
||||||
exist: boolean
|
|
||||||
version: string
|
|
||||||
}
|
|
||||||
gpus: GpuSettingInfo[]
|
gpus: GpuSettingInfo[]
|
||||||
gpu_highest_vram: string
|
|
||||||
gpus_in_use: string[]
|
|
||||||
is_initial: boolean
|
|
||||||
// TODO: This needs to be set based on user toggle in settings
|
// TODO: This needs to be set based on user toggle in settings
|
||||||
vulkan: boolean
|
vulkan: boolean
|
||||||
|
cpu?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GpuSettingInfo = {
|
export type GpuSettingInfo = {
|
||||||
|
activated: boolean
|
||||||
|
free_vram: number
|
||||||
id: string
|
id: string
|
||||||
vram: string
|
|
||||||
name: string
|
name: string
|
||||||
arch?: string
|
total_vram: number
|
||||||
|
uuid: string
|
||||||
|
version: string
|
||||||
|
additional_information?: GpuAdditionalInformation
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SystemInformation = {
|
export type SystemInformation = {
|
||||||
@ -42,9 +34,6 @@ export type SupportedPlatform = SupportedPlatformTuple[number]
|
|||||||
export type OperatingSystemInfo = {
|
export type OperatingSystemInfo = {
|
||||||
platform: SupportedPlatform | 'unknown'
|
platform: SupportedPlatform | 'unknown'
|
||||||
arch: string
|
arch: string
|
||||||
release: string
|
|
||||||
machine: string
|
|
||||||
version: string
|
|
||||||
totalMem: number
|
totalMem: number
|
||||||
freeMem: number
|
freeMem: number
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,7 @@ export type Model = {
|
|||||||
/**
|
/**
|
||||||
* The model identifier, modern version of id.
|
* The model identifier, modern version of id.
|
||||||
*/
|
*/
|
||||||
mode?: string
|
model?: string
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Human-readable name that is used for UI.
|
* Human-readable name that is used for UI.
|
||||||
@ -150,6 +150,7 @@ export type ModelSettingParams = {
|
|||||||
*/
|
*/
|
||||||
export type ModelRuntimeParams = {
|
export type ModelRuntimeParams = {
|
||||||
temperature?: number
|
temperature?: number
|
||||||
|
max_temperature?: number
|
||||||
token_limit?: number
|
token_limit?: number
|
||||||
top_k?: number
|
top_k?: number
|
||||||
top_p?: number
|
top_p?: number
|
||||||
|
|||||||
@ -61,6 +61,7 @@ export interface ModelSibling {
|
|||||||
*/
|
*/
|
||||||
export interface ModelSource {
|
export interface ModelSource {
|
||||||
id: string
|
id: string
|
||||||
|
author?: string
|
||||||
metadata: Metadata
|
metadata: Metadata
|
||||||
models: ModelSibling[]
|
models: ModelSibling[]
|
||||||
type?: string
|
type?: string
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import * as monitoringInterface from './monitoringInterface'
|
|
||||||
import * as resourceInfo from './resourceInfo'
|
|
||||||
|
|
||||||
import * as index from './index'
|
|
||||||
|
|
||||||
it('should re-export all symbols from monitoringInterface and resourceInfo', () => {
|
|
||||||
for (const key in monitoringInterface) {
|
|
||||||
expect(index[key]).toBe(monitoringInterface[key])
|
|
||||||
}
|
|
||||||
for (const key in resourceInfo) {
|
|
||||||
expect(index[key]).toBe(resourceInfo[key])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
export * from './monitoringInterface'
|
|
||||||
export * from './resourceInfo'
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
import { GpuSetting, OperatingSystemInfo } from '../miscellaneous'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Monitoring extension for system monitoring.
|
|
||||||
* @extends BaseExtension
|
|
||||||
*/
|
|
||||||
export interface MonitoringInterface {
|
|
||||||
/**
|
|
||||||
* Returns information about the system resources.
|
|
||||||
* @returns {Promise<any>} A promise that resolves with the system resources information.
|
|
||||||
*/
|
|
||||||
getResourcesInfo(): Promise<any>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current system load.
|
|
||||||
* @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,6 +0,0 @@
|
|||||||
export type ResourceInfo = {
|
|
||||||
mem: {
|
|
||||||
totalMemory: number
|
|
||||||
usedMemory: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
export * from './threadEntity'
|
export * from './threadEntity'
|
||||||
export * from './threadInterface'
|
export * from './threadInterface'
|
||||||
export * from './threadEvent'
|
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
import { ThreadEvent } from './threadEvent';
|
|
||||||
|
|
||||||
it('should have the correct values', () => {
|
|
||||||
expect(ThreadEvent.OnThreadStarted).toBe('OnThreadStarted');
|
|
||||||
});
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export enum ThreadEvent {
|
|
||||||
/** The `OnThreadStarted` event is emitted when a thread is started. */
|
|
||||||
OnThreadStarted = 'OnThreadStarted',
|
|
||||||
}
|
|
||||||
@ -1 +1,3 @@
|
|||||||
GTM_ID=xxxx
|
GTM_ID=xxxx
|
||||||
|
POSTHOG_KEY=xxxx
|
||||||
|
POSTHOG_HOST=xxxx
|
||||||
@ -18,7 +18,7 @@ We try to **keep routes consistent** to maintain SEO.
|
|||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
Refer to the [Contributing Guide](https://github.com/menloresearch/jan/blob/main/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
||||||
|
|
||||||
### Pre-requisites and Installation
|
### Pre-requisites and Installation
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,8 @@ const nextConfig = {
|
|||||||
output: 'export',
|
output: 'export',
|
||||||
env: {
|
env: {
|
||||||
GTM_ID: process.env.GTM_ID,
|
GTM_ID: process.env.GTM_ID,
|
||||||
|
POSTHOG_KEY: process.env.POSTHOG_KEY,
|
||||||
|
POSTHOG_HOST: process.env.POSTHOG_HOST,
|
||||||
},
|
},
|
||||||
transpilePackages: ['@scalar', 'react-tweet'],
|
transpilePackages: ['@scalar', 'react-tweet'],
|
||||||
images: {
|
images: {
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"plop": "^4.0.1",
|
"plop": "^4.0.1",
|
||||||
"plop-helper-date": "^1.0.0",
|
"plop-helper-date": "^1.0.0",
|
||||||
|
"posthog-js": "^1.194.6",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-hook-form": "^7.51.1",
|
"react-hook-form": "^7.51.1",
|
||||||
|
|||||||
BIN
docs/public/assets/images/changelog/jan-v0-5-13.gif
Normal file
BIN
docs/public/assets/images/changelog/jan-v0-5-13.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 MiB |
BIN
docs/public/assets/images/changelog/jan-v0-5-14-deepseek-r1.gif
Normal file
BIN
docs/public/assets/images/changelog/jan-v0-5-14-deepseek-r1.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.0 MiB |
7
docs/public/assets/images/general/menlo.svg
Normal file
7
docs/public/assets/images/general/menlo.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="100" height="26" viewBox="0 0 100 26" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M83.3068 2.47705L84.8212 0.962708H96.4713L97.9856 2.47705L96.4713 4.0086H84.8212L83.3068 2.47705ZM83.3068 23.5229L84.8212 22.0086H96.4713L97.9856 23.5229L96.4713 25.0373H84.8212L83.3068 23.5229ZM97.9856 12.9914L99.4999 14.5229V22.0086L97.9856 23.5229L96.4713 22.0086V14.5229L97.9856 12.9914ZM83.3068 2.47705L81.7925 4.0086V11.4943L83.3068 12.9914L84.8212 11.4943V4.0086L83.3068 2.47705ZM97.9856 2.47705L96.4713 4.0086V11.4943L97.9856 12.9914L99.4999 11.4943V4.0086L97.9856 2.47705ZM83.3068 23.5229L81.7925 22.0086V14.5229L83.3068 12.9914L84.8212 14.5229V22.0086L83.3068 23.5229Z" fill="#FF5C00"/>
|
||||||
|
<path d="M62.4849 2.47705V11.477L63.9992 12.9914L65.5135 11.4943V2.47705L63.9992 0.962708L62.4849 2.47705ZM63.9992 23.5229L62.4849 22.0086V14.5229L63.9992 12.9914L65.5135 14.5229V22.0086L63.9992 23.5229ZM78.678 23.5229L77.1636 25.0373H65.5135L63.9992 23.5229L65.5135 22.0086H77.1636L78.678 23.5229Z" fill="#FF5C00"/>
|
||||||
|
<path d="M41.3184 14.5229L42.8327 13.0086L44.347 14.5229V23.5229L42.8327 25.0373L41.3184 23.5229V14.5229ZM41.3184 2.47705L42.8327 0.962708L44.347 2.47705V11.4943L42.8327 13.0086L41.3184 11.4943V2.47705ZM55.9971 2.47705L57.5115 0.962708L59.0258 2.47705V11.4943L57.5115 13.0086L55.9971 11.4943V2.47705ZM55.9971 14.5229L57.5115 13.0086L59.0258 14.5229V23.5229L57.5115 25.0373L55.9971 23.5229V14.5229ZM44.6568 3.87093L45.4312 1.87475L47.4273 2.66634L51.0239 10.9264L50.2323 12.9226L48.2361 12.131L44.6568 3.87093ZM49.4407 14.9187L50.2323 12.9226L52.2285 13.6969L55.8251 21.957L55.0335 23.9531L53.0373 23.1616L49.4407 14.9187Z" fill="#FF5C00"/>
|
||||||
|
<path d="M23.1808 23.5229L24.6952 22.0086H36.3453L37.8596 23.5229L36.3453 25.0373H24.6952L23.1808 23.5229ZM23.1808 2.47705L24.6952 0.962708H36.3453L37.8596 2.47705L36.3453 4.0086H24.6952L23.1808 2.47705ZM23.1808 12.9914L24.6952 11.477H36.3453L37.8596 12.9914L36.3453 14.5057H24.6952L23.1808 12.9914ZM23.1808 2.47705L21.6665 4.0086V11.4943L23.1808 12.9914L24.6952 11.4943V4.0086L23.1808 2.47705ZM23.1808 23.5229L21.6665 22.0086V14.5229L23.1808 12.9914L24.6952 14.5229V22.0086L23.1808 23.5229Z" fill="#FF5C00"/>
|
||||||
|
<path d="M16.6931 2.45984L18.2075 4.0086V11.4943L16.6931 13.0086L15.1788 11.4943V4.0086L16.6931 2.45984ZM9.34512 2.45984L10.8767 4.0086V11.4943L9.34512 13.0086L7.83078 11.4943V4.0086L9.34512 2.45984ZM2.01434 13.0086L0.5 11.4943V4.0086L2.01434 2.45984L3.52868 4.0086V11.4943L2.01434 13.0086ZM0.5 14.5229L2.01434 13.0086L3.52868 14.5229V23.5229L2.01434 25.0373L0.5 23.5229V14.5229ZM16.6931 25.0373L15.1788 23.5229V14.5229L16.6931 13.0086L18.2075 14.5229V23.5229L16.6931 25.0373ZM3.52868 0.962708H7.83078L9.34512 2.45984L7.83078 4.0086H3.52868L2.01434 2.45984L3.52868 0.962708ZM10.8767 0.962708H15.1788L16.6931 2.45984L15.1788 4.0086H10.8767L9.34512 2.45984L10.8767 0.962708Z" fill="#FF5C00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
File diff suppressed because it is too large
Load Diff
@ -1,129 +1,148 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||||
<url><loc>https://jan.ai</loc><lastmod>2024-09-09T08:19:45.721Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai</loc><lastmod>2025-03-10T05:06:47.876Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/analytics</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/analytics</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/engineering</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/engineering</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/engineering/ci-cd</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/engineering/ci-cd</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/engineering/qa</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/engineering/qa</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/product-design</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/product-design</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/project-management</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/project-management</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/strategy</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/strategy</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/handbook/website-docs</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/handbook/website-docs</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/investors</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/investors</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/team</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/team</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/vision</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/vision</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/about/wall-of-love</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/about/wall-of-love</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/blog</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/blog</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2023-12-21-faster-inference-across-platform</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2023-12-21-faster-inference-across-platform</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-01-16-settings-options-right-panel</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-01-16-settings-options-right-panel</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-01-29-local-api-server</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-01-29-local-api-server</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-02-05-jan-data-folder</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-02-05-jan-data-folder</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-02-26-home-servers-with-helm</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-02-10-jan-is-more-stable</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-03-06-ui-revamp-settings</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-02-26-home-servers-with-helm</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-03-11-import-models</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-03-06-ui-revamp-settings</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-03-19-nitro-tensorrt-llm-extension</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-03-11-import-models</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-04-02-groq-api-integration</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-03-19-nitro-tensorrt-llm-extension</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-04-15-new-mistral-extension</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-04-02-groq-api-integration</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-04-25-llama3-command-r-hugginface</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-04-15-new-mistral-extension</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-05-20-llamacpp-upgrade-new-remote-models</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-04-25-llama3-command-r-hugginface</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-05-28-cohere-aya-23-8b-35b-phi-3-medium</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-05-20-llamacpp-upgrade-new-remote-models</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-06-21-nvidia-nim-support</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-05-28-cohere-aya-23-8b-35b-phi-3-medium</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-07-15-claude-3-5-support</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-06-21-nvidia-nim-support</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/changelog/2024-09-01-llama3-1-gemma2-support</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-07-15-claude-3-5-support</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-09-01-llama3-1-gemma2-support</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/architecture</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-09-17-improved-cpu-performance</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/assistants</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-10-24-jan-stable</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/build-extension</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-11-22-jan-bugs</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-11.14-jan-supports-qwen-coder</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/chat</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-12-03-jan-is-faster</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/init</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-12-05-jan-hot-fix-mac</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/kill</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2024-12-30-jan-new-privacy</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2025-01-06-key-issues-resolved</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/download</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/changelog/2025-01-23-deepseek-r1-jan</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/get</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/list</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/architecture</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/remove</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/assistants</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/start</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/build-extension</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/stop</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/models/update</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/chat</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/ps</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/init</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/pull</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/kill</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/run</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cli/serve</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/download</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/command-line</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/get</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cortex-cpp</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/list</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cortex-llamacpp</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/remove</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cortex-openvino</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/start</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cortex-python</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/stop</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/cortex-tensorrt-llm</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/models/update</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/embeddings</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/ps</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/embeddings/overview</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/pull</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/error-codes</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/run</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/ext-architecture</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cli/serve</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/fine-tuning</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/command-line</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/fine-tuning/overview</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cortex-cpp</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/function-calling</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cortex-llamacpp</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/hardware</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cortex-openvino</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/installation</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cortex-python</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/installation/linux</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/cortex-tensorrt-llm</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/installation/mac</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/embeddings</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/installation/windows</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/embeddings/overview</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/model-operations</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/error-codes</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/model-operations/overview</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/ext-architecture</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/py-library</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/fine-tuning</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/quickstart</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/fine-tuning/overview</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/rag</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/function-calling</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/rag/overview</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/hardware</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/server</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/installation</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/text-generation</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/installation/linux</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/ts-library</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/installation/mac</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/vision</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/installation/windows</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/cortex/vision/overview</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/model-operations</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/model-operations/overview</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/assistants</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/py-library</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/built-in/llama-cpp</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/quickstart</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/built-in/tensorrt-llm</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/rag</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/data-folder</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/rag/overview</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/desktop</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/server</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/desktop/linux</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/text-generation</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/desktop/mac</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/ts-library</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/desktop/windows</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/vision</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/error-codes</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/cortex/vision/overview</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/extensions</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/install-extensions</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/api-server</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/models</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/assistants</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/models/manage-models</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/configure-extensions</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/models/model-parameters</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/data-folder</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/quickstart</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/desktop</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/anthropic</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/desktop/linux</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/azure</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/desktop/mac</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/cohere</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/desktop/windows</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/generic-openai</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/error-codes</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/groq</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/extensions</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/martian</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/extensions-settings/model-management</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/mistralai</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/extensions-settings/system-monitoring</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/nvidia-nim</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/install-engines</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/openai</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/install-extensions</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/openrouter</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/local-engines/llama-cpp</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/remote-models/triton</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/models</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/settings</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/models/manage-models</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/shortcuts</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/models/model-parameters</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/threads</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/privacy</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/tools/retrieval</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/privacy-policy</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/docs/troubleshooting</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/quickstart</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/download</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/anthropic</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/integrations</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/cohere</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/integrations/coding/continue-dev</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/deepseek</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/integrations/function-calling/interpreter</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/google</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/integrations/messaging/llmcord</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/groq</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/integrations/workflow-automation/raycast</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/martian</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/post/benchmarking-nvidia-tensorrt-llm</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/mistralai</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/post/bitdefender</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/nvidia-nim</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/post/data-is-moat</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/openai</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/post/rag-is-not-enough</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/openrouter</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/privacy</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/remote-models/triton</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
<url><loc>https://jan.ai/support</loc><lastmod>2024-09-09T08:19:45.722Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
<url><loc>https://jan.ai/docs/settings</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/docs/threads</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/docs/tools/retrieval</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/docs/troubleshooting</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/download</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations/coding/continue-dev</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations/coding/tabby</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations/function-calling/interpreter</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations/messaging/llmcord</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/integrations/workflow-automation/n8n</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/benchmarking-nvidia-tensorrt-llm</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/bitdefender</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/data-is-moat</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/deepseek-r1-locally</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/offline-chatgpt-alternative</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/rag-is-not-enough</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/post/run-ai-models-locally</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/privacy</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
|
<url><loc>https://jan.ai/support</loc><lastmod>2025-03-10T05:06:47.877Z</lastmod><changefreq>daily</changefreq><priority>1</priority></url>
|
||||||
</urlset>
|
</urlset>
|
||||||
@ -27,7 +27,7 @@ export const APIReference = () => {
|
|||||||
<ApiReferenceReact
|
<ApiReferenceReact
|
||||||
configuration={{
|
configuration={{
|
||||||
spec: {
|
spec: {
|
||||||
url: 'https://raw.githubusercontent.com/janhq/docs/main/public/openapi/jan.json',
|
url: 'https://raw.githubusercontent.com/menloresearch/docs/main/public/openapi/jan.json',
|
||||||
},
|
},
|
||||||
theme: 'alternate',
|
theme: 'alternate',
|
||||||
hideModels: true,
|
hideModels: true,
|
||||||
|
|||||||
@ -57,7 +57,7 @@ const Changelog = () => {
|
|||||||
<p className="text-base mt-2 leading-relaxed">
|
<p className="text-base mt-2 leading-relaxed">
|
||||||
Latest release updates from the Jan team. Check out our
|
Latest release updates from the Jan team. Check out our
|
||||||
<a
|
<a
|
||||||
href="https://github.com/orgs/janhq/projects/5/views/52"
|
href="https://github.com/orgs/menloresearch/projects/5/views/52"
|
||||||
className="text-blue-600 dark:text-blue-400 cursor-pointer"
|
className="text-blue-600 dark:text-blue-400 cursor-pointer"
|
||||||
>
|
>
|
||||||
Roadmap
|
Roadmap
|
||||||
@ -150,7 +150,7 @@ const Changelog = () => {
|
|||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/janhq/jan/releases"
|
href="https://github.com/menloresearch/jan/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center"
|
className="dark:nx-bg-neutral-900 dark:text-white bg-black text-white hover:text-white justify-center dark:border dark:border-neutral-800 flex-shrink-0 px-4 py-3 rounded-xl inline-flex items-center"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -86,7 +86,7 @@ export default function CardDownload({ lastRelease }: Props) {
|
|||||||
.replace('{tag}', tag)
|
.replace('{tag}', tag)
|
||||||
return {
|
return {
|
||||||
...system,
|
...system,
|
||||||
href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -149,7 +149,7 @@ const DropdownDownload = ({ lastRelease }: Props) => {
|
|||||||
.replace('{tag}', tag)
|
.replace('{tag}', tag)
|
||||||
return {
|
return {
|
||||||
...system,
|
...system,
|
||||||
href: `https://github.com/janhq/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
href: `https://github.com/menloresearch/jan/releases/download/${lastRelease.tag_name}/${downloadUrl}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setSystems(updatedSystems)
|
setSystems(updatedSystems)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import ThemeImage from '@/components/ThemeImage'
|
import ThemeImage from '@/components/ThemeImage'
|
||||||
import { AiOutlineGithub } from 'react-icons/ai'
|
import { AiOutlineGithub } from 'react-icons/ai'
|
||||||
import { RiTwitterXFill } from 'react-icons/ri'
|
import { RiTwitterXFill } from 'react-icons/ri'
|
||||||
@ -7,6 +7,7 @@ import { BiLogoDiscordAlt } from 'react-icons/bi'
|
|||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import LogoMark from '@/components/LogoMark'
|
import LogoMark from '@/components/LogoMark'
|
||||||
import { FaLinkedin } from 'react-icons/fa'
|
import { FaLinkedin } from 'react-icons/fa'
|
||||||
|
import posthog from 'posthog-js'
|
||||||
|
|
||||||
const socials = [
|
const socials = [
|
||||||
{
|
{
|
||||||
@ -25,7 +26,7 @@ const socials = [
|
|||||||
icon: (
|
icon: (
|
||||||
<AiOutlineGithub className="text-lg text-black/60 dark:text-white/60" />
|
<AiOutlineGithub className="text-lg text-black/60 dark:text-white/60" />
|
||||||
),
|
),
|
||||||
href: 'https://github.com/janhq/jan',
|
href: 'https://github.com/menloresearch/jan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <FaLinkedin className="text-lg text-black/60 dark:text-white/60" />,
|
icon: <FaLinkedin className="text-lg text-black/60 dark:text-white/60" />,
|
||||||
@ -61,7 +62,7 @@ const menus = [
|
|||||||
child: [
|
child: [
|
||||||
{
|
{
|
||||||
menu: 'Github',
|
menu: 'Github',
|
||||||
path: 'https://github.com/janhq/jan',
|
path: 'https://github.com/menloresearch/jan',
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -94,7 +95,7 @@ const menus = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
menu: 'Careers',
|
menu: 'Careers',
|
||||||
path: 'https://homebrew.bamboohr.com/careers',
|
path: 'https://menlo.bamboohr.com/careers',
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -104,6 +105,19 @@ const menus = [
|
|||||||
const getCurrentYear = new Date().getFullYear()
|
const getCurrentYear = new Date().getFullYear()
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
posthog.init(process.env.POSTHOG_KEY as string, {
|
||||||
|
api_host: process.env.POSTHOG_HOST,
|
||||||
|
disable_session_recording: true,
|
||||||
|
person_profiles: 'always',
|
||||||
|
persistence: 'localStorage',
|
||||||
|
})
|
||||||
|
|
||||||
|
posthog.capture('web_page_view', { timestamp: new Date() })
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const { register, handleSubmit, reset } = useForm({
|
const { register, handleSubmit, reset } = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: '',
|
email: '',
|
||||||
@ -237,14 +251,14 @@ export default function Footer() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<span>©{getCurrentYear} Homebrew Computer Company</span>
|
<span>©{getCurrentYear} Menlo Research</span>
|
||||||
<ThemeImage
|
<ThemeImage
|
||||||
source={{
|
source={{
|
||||||
light: '/assets/images/general/homebrew-dark.svg',
|
light: '/assets/images/general/menlo.svg',
|
||||||
dark: '/assets/images/general/homebrew-white.svg',
|
dark: '/assets/images/general/menlo.svg',
|
||||||
}}
|
}}
|
||||||
alt="App screenshots"
|
alt="App screenshots"
|
||||||
width={140}
|
width={80}
|
||||||
height={200}
|
height={200}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -23,7 +23,7 @@ const BuiltWithLove = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
|
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
|
||||||
<a
|
<a
|
||||||
href="https://github.com/janhq/jan"
|
href="https://github.com/menloresearch/jan"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 "
|
className="dark:bg-white bg-black inline-flex w-56 px-4 py-3 rounded-xl cursor-pointer justify-center items-start space-x-4 "
|
||||||
>
|
>
|
||||||
|
|||||||
@ -44,7 +44,7 @@ const Hero = () => {
|
|||||||
<div className="mt-10 text-center">
|
<div className="mt-10 text-center">
|
||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/janhq/jan/releases"
|
href="https://github.com/menloresearch/jan/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="hidden lg:inline-block"
|
className="hidden lg:inline-block"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import '@code-hike/mdx/styles.css'
|
|||||||
import { Fragment } from "react"
|
import { Fragment } from "react"
|
||||||
import Script from "next/script"
|
import Script from "next/script"
|
||||||
|
|
||||||
|
|
||||||
export default function App({ Component, pageProps }) {
|
export default function App({ Component, pageProps }) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|||||||
@ -26,19 +26,19 @@ Jan operates on open-source principles, giving everyone the freedom to adjust, p
|
|||||||
|
|
||||||
We embrace open development, showcasing our progress and upcoming features on GitHub, and we encourage your input and contributions:
|
We embrace open development, showcasing our progress and upcoming features on GitHub, and we encourage your input and contributions:
|
||||||
|
|
||||||
- [Jan Framework](https://github.com/janhq/jan) (AGPLv3)
|
- [Jan Framework](https://github.com/menloresearch/jan) (AGPLv3)
|
||||||
- [Jan Desktop Client & Local server](https://jan.ai) (AGPLv3, built on Jan Framework)
|
- [Jan Desktop Client & Local server](https://jan.ai) (AGPLv3, built on Jan Framework)
|
||||||
- [Nitro: run Local AI](https://github.com/janhq/nitro) (AGPLv3)
|
- [Nitro: run Local AI](https://github.com/menloresearch/nitro) (AGPLv3)
|
||||||
|
|
||||||
## Build in Public
|
## Build in Public
|
||||||
|
|
||||||
We use GitHub to build in public and welcome anyone to join in.
|
We use GitHub to build in public and welcome anyone to join in.
|
||||||
|
|
||||||
- [Jan's Kanban](https://github.com/orgs/janhq/projects/5)
|
- [Jan's Kanban](https://github.com/orgs/menloresearch/projects/5)
|
||||||
- [Jan's Roadmap](https://github.com/orgs/janhq/projects/5/views/29)
|
- [Jan's Roadmap](https://github.com/orgs/menloresearch/projects/5/views/29)
|
||||||
|
|
||||||
## Collaboration
|
## Collaboration
|
||||||
|
|
||||||
Our team spans the globe, working remotely to bring Jan to life. We coordinate through Discord and GitHub, valuing asynchronous communication and minimal, purposeful meetings. For collaboration and brainstorming, we utilize tools like [Excalidraw](https://excalidraw.com/) and [Miro](https://miro.com/), ensuring alignment and shared vision through visual storytelling and detailed documentation on [HackMD](https://hackmd.io/).
|
Our team spans the globe, working remotely to bring Jan to life. We coordinate through Discord and GitHub, valuing asynchronous communication and minimal, purposeful meetings. For collaboration and brainstorming, we utilize tools like [Excalidraw](https://excalidraw.com/) and [Miro](https://miro.com/), ensuring alignment and shared vision through visual storytelling and detailed documentation on [HackMD](https://hackmd.io/).
|
||||||
|
|
||||||
Check out the [Jan Framework](https://github.com/janhq/jan) and our desktop client & local server at [jan.ai](https://jan.ai), both licensed under AGPLv3 for maximum openness and user freedom.
|
Check out the [Jan Framework](https://github.com/menloresearch/jan) and our desktop client & local server at [jan.ai](https://jan.ai), both licensed under AGPLv3 for maximum openness and user freedom.
|
||||||
|
|||||||
@ -19,5 +19,5 @@ keywords:
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [Requirements](https://github.com/janhq/jan?tab=readme-ov-file#requirements-for-running-jan)
|
- [Requirements](https://github.com/menloresearch/jan?tab=readme-ov-file#requirements-for-running-jan)
|
||||||
- [Setting up local env](https://github.com/janhq/jan?tab=readme-ov-file#contributing)
|
- [Setting up local env](https://github.com/menloresearch/jan?tab=readme-ov-file#contributing)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { Callout } from 'nextra/components'
|
|||||||
|
|
||||||
# Project Management
|
# Project Management
|
||||||
|
|
||||||
We use the [Jan Monorepo Project](https://github.com/orgs/janhq/projects/5) in Github to manage our roadmap and sprint Kanbans.
|
We use the [Jan Monorepo Project](https://github.com/orgs/menloresearch/projects/5) in Github to manage our roadmap and sprint Kanbans.
|
||||||
|
|
||||||
As much as possible, everyone owns their respective `epics` and `tasks`.
|
As much as possible, everyone owns their respective `epics` and `tasks`.
|
||||||
|
|
||||||
@ -30,37 +30,37 @@ As much as possible, everyone owns their respective `epics` and `tasks`.
|
|||||||
|
|
||||||
## Quicklinks
|
## Quicklinks
|
||||||
|
|
||||||
- [High-level roadmap](https://github.com/orgs/janhq/projects/5/views/16): view used at at strategic level, for team wide alignment. Start & end dates reflect engineering implementation cycles. Typically product & design work preceeds these timelines.
|
- [High-level roadmap](https://github.com/orgs/menloresearch/projects/5/views/16): view used at at strategic level, for team wide alignment. Start & end dates reflect engineering implementation cycles. Typically product & design work preceeds these timelines.
|
||||||
- [Standup Kanban](https://github.com/orgs/janhq/projects/5/views/25): view used during daily standup. Sprints should be up to date.
|
- [Standup Kanban](https://github.com/orgs/menloresearch/projects/5/views/25): view used during daily standup. Sprints should be up to date.
|
||||||
|
|
||||||
## Organization
|
## Organization
|
||||||
|
|
||||||
[`Roadmap Labels`](https://github.com/janhq/jan/labels?q=roadmap)
|
[`Roadmap Labels`](https://github.com/menloresearch/jan/labels?q=roadmap)
|
||||||
|
|
||||||
- `Roadmap Labels` tag large, long-term, & strategic projects that can span multiple teams and multiple sprints
|
- `Roadmap Labels` tag large, long-term, & strategic projects that can span multiple teams and multiple sprints
|
||||||
- Example label: `roadmap: Jan has Mobile`
|
- Example label: `roadmap: Jan has Mobile`
|
||||||
- `Roadmaps` contain `epics`
|
- `Roadmaps` contain `epics`
|
||||||
|
|
||||||
[`Epics`](https://github.com/janhq/jan/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+epic%22)
|
[`Epics`](https://github.com/menloresearch/jan/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+epic%22)
|
||||||
|
|
||||||
- `Epics` track large stories that span 1-2 weeks, and it outlines specs, architecture decisions, designs
|
- `Epics` track large stories that span 1-2 weeks, and it outlines specs, architecture decisions, designs
|
||||||
- `Epics` contain `tasks`
|
- `Epics` contain `tasks`
|
||||||
- `Epics` should always have 1 owner
|
- `Epics` should always have 1 owner
|
||||||
|
|
||||||
[`Milestones`](https://github.com/janhq/jan/milestones)
|
[`Milestones`](https://github.com/menloresearch/jan/milestones)
|
||||||
|
|
||||||
- `Milestones` track release versions. We use [semantic versioning](https://semver.org/)
|
- `Milestones` track release versions. We use [semantic versioning](https://semver.org/)
|
||||||
- `Milestones` span ~2 weeks and have deadlines
|
- `Milestones` span ~2 weeks and have deadlines
|
||||||
- `Milestones` usually fit within 2-week sprint cycles
|
- `Milestones` usually fit within 2-week sprint cycles
|
||||||
|
|
||||||
[`Tasks`](https://github.com/janhq/jan/issues)
|
[`Tasks`](https://github.com/menloresearch/jan/issues)
|
||||||
|
|
||||||
- Tasks are individual issues (feats, bugs, chores) that can be completed within a few days
|
- Tasks are individual issues (feats, bugs, chores) that can be completed within a few days
|
||||||
- Tasks, except for critical bugs, should always belong to an `epic` (and thus fit into our roadmap)
|
- Tasks, except for critical bugs, should always belong to an `epic` (and thus fit into our roadmap)
|
||||||
- Tasks are usually named per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)
|
- Tasks are usually named per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)
|
||||||
- Tasks should always have 1 owner
|
- Tasks should always have 1 owner
|
||||||
|
|
||||||
We aim to always sprint on `tasks` that are a part of the [current roadmap](https://github.com/orgs/janhq/projects/5/views/16).
|
We aim to always sprint on `tasks` that are a part of the [current roadmap](https://github.com/orgs/menloresearch/projects/5/views/16).
|
||||||
|
|
||||||
## Kanban
|
## Kanban
|
||||||
|
|
||||||
@ -80,4 +80,4 @@ We aim to always sprint on `tasks` that are a part of the [current roadmap](http
|
|||||||
|
|
||||||
As a result, our feature prioritization can feel a bit black box at times.
|
As a result, our feature prioritization can feel a bit black box at times.
|
||||||
|
|
||||||
We'd appreciate high quality insights and volunteers for user interviews through [Discord](https://discord.gg/af6SaTdzpx) and [Github](https://github.com/janhq).
|
We'd appreciate high quality insights and volunteers for user interviews through [Discord](https://discord.gg/af6SaTdzpx) and [Github](https://github.com/menloresearch).
|
||||||
|
|||||||
@ -37,7 +37,7 @@ We try to **keep routes consistent** to maintain SEO.
|
|||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
Refer to the [Contributing Guide](https://github.com/janhq/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
Refer to the [Contributing Guide](https://github.com/menloresearch/jan/blob/dev/CONTRIBUTING.md) for more comprehensive information on how to contribute to the Jan project.
|
||||||
|
|
||||||
## Pre-requisites and Installation
|
## Pre-requisites and Installation
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: Homebrew Computer Company
|
title: Menlo Research
|
||||||
description: We are Homebrew Computer Company, the creators and maintainers of Jan, Cortex and other tools.
|
description: We are Menlo Research, the creators and maintainers of Jan, Cortex and other tools.
|
||||||
keywords:
|
keywords:
|
||||||
[
|
[
|
||||||
Homebrew Computer Company,
|
Menlo Research,
|
||||||
Jan,
|
Jan,
|
||||||
local AI,
|
local AI,
|
||||||
open-source alternative to chatgpt,
|
open-source alternative to chatgpt,
|
||||||
@ -22,7 +22,7 @@ keywords:
|
|||||||
|
|
||||||
import { Callout } from 'nextra/components'
|
import { Callout } from 'nextra/components'
|
||||||
|
|
||||||
# Homebrew Computer Company
|
# Menlo Research
|
||||||
|
|
||||||

|

|
||||||
_[Eniac](https://www.computerhistory.org/revolution/birth-of-the-computer/4/78), the World's First Computer (Photo courtesy of US Army)_
|
_[Eniac](https://www.computerhistory.org/revolution/birth-of-the-computer/4/78), the World's First Computer (Photo courtesy of US Army)_
|
||||||
@ -36,7 +36,7 @@ We're a team of AI researchers and engineers. We are the creators and lead maint
|
|||||||
- More to come!
|
- More to come!
|
||||||
|
|
||||||
<Callout>
|
<Callout>
|
||||||
The [Homebrew Computer Club](https://en.wikipedia.org/wiki/Homebrew_Computer_Club) was an early computer hobbyist group from 1975 to 1986 that led to Apple and the personal computer revolution.
|
The [Menlo Research](https://en.wikipedia.org/wiki/Homebrew_Computer_Club) was an early computer hobbyist group from 1975 to 1986 that led to Apple and the personal computer revolution.
|
||||||
</Callout>
|
</Callout>
|
||||||
|
|
||||||
### Mission
|
### Mission
|
||||||
@ -81,7 +81,7 @@ Our products are designed with [Extension APIs](/docs/extensions), and we do our
|
|||||||
|
|
||||||
We are part of a larger open-source community and are committed to being a good jigsaw puzzle piece. We credit and actively contribute to upstream projects.
|
We are part of a larger open-source community and are committed to being a good jigsaw puzzle piece. We credit and actively contribute to upstream projects.
|
||||||
|
|
||||||
We adopt a public-by-default approach to [Project Management](https://github.com/orgs/janhq/projects/5), [Roadmaps](https://github.com/orgs/janhq/projects/5/views/31), and Helpdesk for our products.
|
We adopt a public-by-default approach to [Project Management](https://github.com/orgs/menloresearch/projects/5), [Roadmaps](https://github.com/orgs/menloresearch/projects/5/views/31), and Helpdesk for our products.
|
||||||
|
|
||||||
## Inspirations
|
## Inspirations
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ We are inspired by and actively try to emulate the paths of companies we admire
|
|||||||
- [Obsidian](https://obsidian.md/)
|
- [Obsidian](https://obsidian.md/)
|
||||||
- [Discourse](https://www.discourse.org/about)
|
- [Discourse](https://www.discourse.org/about)
|
||||||
- [Gitlab](https://handbook.gitlab.com/handbook/company/history/#2017-gitlab-storytime)
|
- [Gitlab](https://handbook.gitlab.com/handbook/company/history/#2017-gitlab-storytime)
|
||||||
- [Redhat](https://www.redhat.com/en/about/development-model)
|
- [Red Hat](https://www.redhat.com/en/about/development-model)
|
||||||
- [Ghost](https://ghost.org/docs/contributing/)
|
- [Ghost](https://ghost.org/docs/contributing/)
|
||||||
- [Lago](https://www.getlago.com/blog/open-source-licensing-and-why-lago-chose-agplv3)
|
- [Lago](https://www.getlago.com/blog/open-source-licensing-and-why-lago-chose-agplv3)
|
||||||
- [Twenty](https://twenty.com/story)
|
- [Twenty](https://twenty.com/story)
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { Cards, Card } from 'nextra/components'
|
|||||||
|
|
||||||
We're a small, fully-remote team, mostly based in Southeast Asia.
|
We're a small, fully-remote team, mostly based in Southeast Asia.
|
||||||
|
|
||||||
We are committed to become a global company. You can check our [Careers page](https://homebrew.bamboohr.com/careers) if you'd like to join us on our adventure.
|
We are committed to become a global company. You can check our [Careers page](https://menlo.bamboohr.com/careers) if you'd like to join us on our adventure.
|
||||||
|
|
||||||
<Callout emoji="🌏">
|
<Callout emoji="🌏">
|
||||||
Ping us in [Discord](https://discord.gg/AAGQNpJQtH) if you're keen to talk to us!
|
Ping us in [Discord](https://discord.gg/AAGQNpJQtH) if you're keen to talk to us!
|
||||||
|
|||||||
@ -24,4 +24,4 @@ Fixes 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.5).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.5).
|
||||||
@ -24,4 +24,4 @@ Jan now supports Mistral's new model Codestral. Thanks [Bartowski](https://huggi
|
|||||||
|
|
||||||
More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov)
|
More GGUF models can run in Jan - we rebased to llama.cpp b3012.Big thanks to [ggerganov](https://github.com/ggerganov)
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.0).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.0).
|
||||||
|
|||||||
@ -28,4 +28,4 @@ Jan now understands LaTeX, allowing users to process and understand complex math
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.4.12).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.12).
|
||||||
|
|||||||
@ -28,4 +28,4 @@ Users can now connect to OpenAI's new model GPT-4o.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
For more details, see the [GitHub release notes.](https://github.com/janhq/jan/releases/tag/v0.4.13)
|
For more details, see the [GitHub release notes.](https://github.com/menloresearch/jan/releases/tag/v0.4.13)
|
||||||
|
|||||||
@ -16,4 +16,4 @@ More GGUF models can run in Jan - we rebased to llama.cpp b2961.
|
|||||||
|
|
||||||
Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models.
|
Huge shoutouts to [ggerganov](https://github.com/ggerganov) and contributors for llama.cpp, and [Bartowski](https://huggingface.co/bartowski) for GGUF models.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.4.14).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.4.14).
|
||||||
|
|||||||
@ -26,4 +26,4 @@ We've updated to llama.cpp b3088 for better performance - thanks to [GG](https:/
|
|||||||
- Reduced chat font weight (back to normal!)
|
- Reduced chat font weight (back to normal!)
|
||||||
- Restored the maximize button
|
- Restored the maximize button
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.1).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.1).
|
||||||
|
|||||||
@ -32,4 +32,4 @@ We've restored the tooltip hover functionality, which makes it easier to access
|
|||||||
|
|
||||||
The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix.
|
The right-click options for thread settings are now fully operational again. You can now manage your threads with this fix.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.2).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.2).
|
||||||
|
|||||||
@ -23,4 +23,4 @@ We've been working on stability issues over the last few weeks. Jan is now more
|
|||||||
- Fixed the GPU memory utilization bar
|
- Fixed the GPU memory utilization bar
|
||||||
- Some UX and copy improvements
|
- Some UX and copy improvements
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.3).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.3).
|
||||||
|
|||||||
@ -32,4 +32,4 @@ Switching between threads used to reset your instruction settings. That’s fixe
|
|||||||
### Minor UI Tweaks & Bug Fixes
|
### Minor UI Tweaks & Bug Fixes
|
||||||
We’ve also resolved issues with the input slider on the right panel and tackled several smaller bugs to keep everything running smoothly.
|
We’ve also resolved issues with the input slider on the right panel and tackled several smaller bugs to keep everything running smoothly.
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.4).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.4).
|
||||||
|
|||||||
@ -23,4 +23,4 @@ Fixes 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.7).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.7).
|
||||||
@ -22,4 +22,4 @@ Jan v0.5.9 is here: fixing what needed fixing
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.9).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.9).
|
||||||
@ -22,4 +22,4 @@ and various UI/UX enhancements 💫
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.8).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.8).
|
||||||
@ -19,4 +19,4 @@ Jan v0.5.10 is live: Jan is faster, smoother, and more reliable.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.10).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.10).
|
||||||
@ -23,4 +23,4 @@ Jan v0.5.11 is here - critical issues fixed, Mac installation updated.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.11).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.11).
|
||||||
@ -25,4 +25,4 @@ Jan v0.5.11 is here - critical issues fixed, Mac installation updated.
|
|||||||
|
|
||||||
Update your product or download the latest: https://jan.ai
|
Update your product or download the latest: https://jan.ai
|
||||||
|
|
||||||
For more details, see the [GitHub release notes](https://github.com/janhq/jan/releases/tag/v0.5.12).
|
For more details, see the [GitHub release notes](https://github.com/menloresearch/jan/releases/tag/v0.5.12).
|
||||||
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