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:
|
||||
- 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"
|
||||
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:
|
||||
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:
|
||||
uses: ./.github/workflows/template-build-macos.yml
|
||||
secrets: inherit
|
||||
@ -43,6 +18,8 @@ jobs:
|
||||
public_provider: github
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
beta: true
|
||||
nightly: false
|
||||
cortex_api_port: "39271"
|
||||
|
||||
build-windows-x64:
|
||||
uses: ./.github/workflows/template-build-windows-x64.yml
|
||||
@ -53,6 +30,8 @@ jobs:
|
||||
public_provider: github
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
beta: true
|
||||
nightly: false
|
||||
cortex_api_port: "39271"
|
||||
|
||||
build-linux-x64:
|
||||
uses: ./.github/workflows/template-build-linux-x64.yml
|
||||
@ -63,9 +42,11 @@ jobs:
|
||||
public_provider: github
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
beta: true
|
||||
nightly: false
|
||||
cortex_api_port: "39271"
|
||||
|
||||
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
|
||||
permissions:
|
||||
contents: write
|
||||
@ -82,19 +63,15 @@ jobs:
|
||||
AWS_DEFAULT_REGION: ${{ secrets.DELTA_AWS_REGION }}
|
||||
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:
|
||||
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
|
||||
steps:
|
||||
- name: Set version to environment variable
|
||||
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
|
||||
uses: Ilshidur/action-discord@master
|
||||
@ -105,6 +82,5 @@ jobs:
|
||||
- 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 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:
|
||||
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 }}
|
||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
nightly: true
|
||||
beta: false
|
||||
cortex_api_port: "39261"
|
||||
|
||||
build-windows-x64:
|
||||
uses: ./.github/workflows/template-build-windows-x64.yml
|
||||
@ -64,8 +67,9 @@ jobs:
|
||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
|
||||
nightly: true
|
||||
beta: false
|
||||
cortex_api_port: "39261"
|
||||
build-linux-x64:
|
||||
uses: ./.github/workflows/template-build-linux-x64.yml
|
||||
secrets: inherit
|
||||
@ -74,6 +78,9 @@ jobs:
|
||||
ref: ${{ needs.set-public-provider.outputs.ref }}
|
||||
public_provider: ${{ needs.set-public-provider.outputs.public_provider }}
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
nightly: true
|
||||
beta: false
|
||||
cortex_api_port: "39261"
|
||||
|
||||
sync-temp-to-latest:
|
||||
needs: [set-public-provider, build-windows-x64, build-linux-x64, build-macos]
|
||||
@ -141,4 +148,3 @@ jobs:
|
||||
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})."
|
||||
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:
|
||||
ref: ${{ github.ref }}
|
||||
public_provider: github
|
||||
beta: false
|
||||
nightly: false
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
build-windows-x64:
|
||||
@ -49,6 +51,8 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
public_provider: github
|
||||
beta: false
|
||||
nightly: false
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
build-linux-x64:
|
||||
@ -58,6 +62,8 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
public_provider: github
|
||||
beta: false
|
||||
nightly: false
|
||||
new_version: ${{ needs.get-update-version.outputs.new_version }}
|
||||
|
||||
update_release_draft:
|
||||
@ -82,4 +88,4 @@ jobs:
|
||||
# config-name: my-config.yml
|
||||
# disable-autolabeler: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
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)
|
||||
|
||||
get_asset_count() {
|
||||
@ -89,39 +89,39 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
|
||||
- name: Wait for CI to pass
|
||||
env:
|
||||
- name: Wait for CI to pass
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
run: |
|
||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||
while true; do
|
||||
ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt')
|
||||
if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then
|
||||
echo "CI is still running, waiting..."
|
||||
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
|
||||
run: |
|
||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||
while true; do
|
||||
ci_completed=$(gh pr checks $pr_number --json completedAt --jq '.[].completedAt')
|
||||
if echo "$ci_completed" | grep -q "0001-01-01T00:00:00Z"; then
|
||||
echo "CI is still running, waiting..."
|
||||
sleep 60
|
||||
else
|
||||
echo "CI passed, merging PR..."
|
||||
break
|
||||
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
|
||||
echo "CI passed, merging PR..."
|
||||
break
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
- name: Merge the PR
|
||||
env:
|
||||
- name: Merge the PR
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
run: |
|
||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||
gh pr merge $pr_number --merge --admin
|
||||
run: |
|
||||
pr_number=${{ needs.update-submodule.outputs.pr_number }}
|
||||
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
|
||||
type: boolean
|
||||
default: false
|
||||
nightly:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
cortex_api_port:
|
||||
required: false
|
||||
type: string
|
||||
default: null
|
||||
secrets:
|
||||
DELTA_AWS_S3_BUCKET_NAME:
|
||||
required: false
|
||||
@ -43,6 +51,31 @@ jobs:
|
||||
with:
|
||||
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
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
@ -83,7 +116,7 @@ jobs:
|
||||
cat ./electron/package.json
|
||||
echo "------------------------"
|
||||
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
|
||||
cat electron/package.json
|
||||
|
||||
@ -115,6 +148,7 @@ jobs:
|
||||
AWS_MAX_ATTEMPTS: '5'
|
||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||
|
||||
- name: Build and publish app to github
|
||||
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
|
||||
with:
|
||||
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
|
||||
type: boolean
|
||||
default: false
|
||||
nightly:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
cortex_api_port:
|
||||
required: false
|
||||
type: string
|
||||
default: null
|
||||
secrets:
|
||||
DELTA_AWS_S3_BUCKET_NAME:
|
||||
required: false
|
||||
@ -52,6 +60,30 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
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
|
||||
uses: actions/setup-node@v1
|
||||
@ -99,7 +131,7 @@ jobs:
|
||||
cat ./electron/package.json
|
||||
echo "------------------------"
|
||||
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
|
||||
cat electron/package.json
|
||||
|
||||
@ -154,6 +186,7 @@ jobs:
|
||||
AWS_MAX_ATTEMPTS: '5'
|
||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||
|
||||
- name: Build and publish app to github
|
||||
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
|
||||
with:
|
||||
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
|
||||
type: boolean
|
||||
default: false
|
||||
nightly:
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
cortex_api_port:
|
||||
required: false
|
||||
type: string
|
||||
default: null
|
||||
secrets:
|
||||
DELTA_AWS_S3_BUCKET_NAME:
|
||||
required: false
|
||||
@ -52,6 +60,30 @@ jobs:
|
||||
with:
|
||||
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
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
@ -108,7 +140,7 @@ jobs:
|
||||
cat ./package.json
|
||||
echo "------------------------"
|
||||
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
|
||||
cat electron/package.json
|
||||
|
||||
@ -153,6 +185,7 @@ jobs:
|
||||
AWS_MAX_ATTEMPTS: '5'
|
||||
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
|
||||
POSTHOG_HOST: ${{ secrets.POSTHOG_HOST }}
|
||||
CORTEX_API_PORT: ${{ inputs.cortex_api_port }}
|
||||
|
||||
- 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
|
||||
@ -193,4 +226,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jan-win-x64-${{ inputs.new_version }}
|
||||
path: ./electron/dist/*.exe
|
||||
path: ./electron/dist/*.exe
|
||||
@ -13,46 +13,46 @@ jobs:
|
||||
outputs:
|
||||
new_version: ${{ steps.version_update.outputs.new_version }}
|
||||
steps:
|
||||
- name: Install jq
|
||||
uses: dcarbone/install-jq-action@v2.0.1
|
||||
- name: Install jq
|
||||
uses: dcarbone/install-jq-action@v2.0.1
|
||||
|
||||
- name: Get tag
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
- name: Get tag
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
id: tag
|
||||
uses: dawidd6/action-get-tag@v1
|
||||
|
||||
- name: Update app version based on latest release tag with build number
|
||||
id: version_update
|
||||
run: |
|
||||
# Function to get the latest release tag
|
||||
get_latest_tag() {
|
||||
local retries=0
|
||||
local max_retries=3
|
||||
local tag
|
||||
while [ $retries -lt $max_retries ]; do
|
||||
tag=$(curl -s https://api.github.com/repos/janhq/jan/releases/latest | jq -r .tag_name)
|
||||
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
||||
echo $tag
|
||||
return
|
||||
else
|
||||
let retries++
|
||||
echo "Retrying... ($retries/$max_retries)"
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
echo "Failed to fetch latest tag after $max_retries attempts."
|
||||
exit 1
|
||||
}
|
||||
- name: Update app version based on latest release tag with build number
|
||||
id: version_update
|
||||
run: |
|
||||
# Function to get the latest release tag
|
||||
get_latest_tag() {
|
||||
local retries=0
|
||||
local max_retries=3
|
||||
local tag
|
||||
while [ $retries -lt $max_retries ]; do
|
||||
tag=$(curl -s https://api.github.com/repos/menloresearch/jan/releases/latest | jq -r .tag_name)
|
||||
if [ -n "$tag" ] && [ "$tag" != "null" ]; then
|
||||
echo $tag
|
||||
return
|
||||
else
|
||||
let retries++
|
||||
echo "Retrying... ($retries/$max_retries)"
|
||||
sleep 2
|
||||
fi
|
||||
done
|
||||
echo "Failed to fetch latest tag after $max_retries attempts."
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then
|
||||
echo "Tag detected, set output follow tag"
|
||||
echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}"
|
||||
else
|
||||
# Get the latest release tag from GitHub API
|
||||
LATEST_TAG=$(get_latest_tag)
|
||||
|
||||
# Remove the 'v' and append the build number to the version
|
||||
new_version="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
||||
echo "New version: $new_version"
|
||||
echo "::set-output name=new_version::$new_version"
|
||||
fi
|
||||
if ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}; then
|
||||
echo "Tag detected, set output follow tag"
|
||||
echo "::set-output name=new_version::${{ steps.tag.outputs.tag }}"
|
||||
else
|
||||
# Get the latest release tag from GitHub API
|
||||
LATEST_TAG=$(get_latest_tag)
|
||||
|
||||
# Remove the 'v' and append the build number to the version
|
||||
new_version="${LATEST_TAG#v}-${GITHUB_RUN_NUMBER}"
|
||||
echo "New version: $new_version"
|
||||
echo "::set-output name=new_version::$new_version"
|
||||
fi
|
||||
|
||||
@ -34,7 +34,7 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: "0"
|
||||
fetch-depth: '0'
|
||||
token: ${{ secrets.PAT_SERVICE_ACCOUNT }}
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
@ -51,6 +51,6 @@ jobs:
|
||||
- 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 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:
|
||||
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
|
||||
|
||||
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/janhq/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).
|
||||
- **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/menloresearch/jan/issues/new).
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
@ -29,4 +29,4 @@ First off, thank you for considering contributing to jan. It's people like you t
|
||||
|
||||
## 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">
|
||||
<!-- 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 Last Commit" src="https://img.shields.io/github/last-commit/janhq/jan"/>
|
||||
<img alt="Github Contributors" src="https://img.shields.io/github/contributors/janhq/jan"/>
|
||||
<img alt="GitHub closed issues" src="https://img.shields.io/github/issues-closed/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/menloresearch/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/menloresearch/jan"/>
|
||||
<img alt="Discord" src="https://img.shields.io/discord/1107178041848909847?label=discord"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://jan.ai/docs/quickstart">Getting Started</a>
|
||||
- <a href="https://jan.ai/docs">Docs</a>
|
||||
- <a href="https://github.com/janhq/jan/releases">Changelog</a>
|
||||
- <a href="https://github.com/janhq/jan/issues">Bug reports</a>
|
||||
- <a href="https://github.com/menloresearch/jan/releases">Changelog</a>
|
||||
- <a href="https://github.com/menloresearch/jan/issues">Bug reports</a>
|
||||
- <a href="https://discord.gg/AsJ8krTT3N">Discord</a>
|
||||
</p>
|
||||
|
||||
@ -23,10 +23,9 @@
|
||||
⚠️ <b> Jan is currently in Development</b>: Expect breaking changes and bugs!
|
||||
</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 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:
|
||||
|
||||
- [x] NVIDIA GPUs (fast)
|
||||
@ -36,7 +35,8 @@ From PCs to multi-GPU clusters, Jan & Cortex supports universal architectures:
|
||||
- [x] Windows x64
|
||||
|
||||
#### 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
|
||||
- Local API Server with OpenAI-equivalent API
|
||||
- [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">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</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">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</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">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<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>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</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
|
||||
|
||||
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
|
||||
|
||||
### Jan
|
||||
|
||||
- [Jan Website](https://jan.ai/)
|
||||
- [Jan GitHub](https://github.com/janhq/jan)
|
||||
- [Jan GitHub](https://github.com/menloresearch/jan)
|
||||
- [Documentation](https://jan.ai/docs)
|
||||
- [Jan Changelog](https://jan.ai/changelog)
|
||||
- [Jan Blog](https://jan.ai/blog)
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
- [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/)
|
||||
- [Models Library](https://cortex.so/models)
|
||||
- API Reference: *Under development*
|
||||
|
||||
- API Reference: _Under development_
|
||||
|
||||
## Requirements for running Jan
|
||||
|
||||
- **MacOS**: 13 or higher
|
||||
@ -179,17 +179,17 @@ Jan is powered by **Cortex.cpp**. It is a C++ command-line interface (CLI) desig
|
||||
## Troubleshooting
|
||||
|
||||
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 NVIDIA GPU](https://jan.ai/docs/troubleshooting#troubleshooting-nvidia-gpu)
|
||||
- [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:
|
||||
|
||||
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.
|
||||
|
||||
*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
|
||||
|
||||
@ -206,7 +206,7 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
||||
1. **Clone the repository and prepare:**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/janhq/jan
|
||||
git clone https://github.com/menloresearch/jan
|
||||
cd jan
|
||||
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.
|
||||
|
||||
|
||||
|
||||
### For production build
|
||||
|
||||
```bash
|
||||
@ -244,7 +242,7 @@ Jan builds on top of other open-source projects:
|
||||
|
||||
- Bugs & requests: file a GitHub ticket
|
||||
- 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
|
||||
|
||||
## Trust & Safety
|
||||
@ -254,7 +252,7 @@ Beware of scams!
|
||||
- We will never request your personal information.
|
||||
- Our product is completely free; no paid version exists.
|
||||
- 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
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
</screenshots>
|
||||
|
||||
<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" />
|
||||
|
||||
|
||||
@ -8,37 +8,38 @@
|
||||
|
||||
```js
|
||||
// Web / extension runtime
|
||||
import * as core from "@janhq/core";
|
||||
import * as core from '@janhq/core'
|
||||
|
||||
// Node runtime
|
||||
import * as node from "@janhq/core/node";
|
||||
import * as node from '@janhq/core/node'
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
1. Open `index.ts` in your code editor.
|
||||
2. Rename the extension class from `SampleExtension` to your preferred extension name.
|
||||
3. Import modules from the core package.
|
||||
```ts
|
||||
import * as core from "@janhq/core";
|
||||
import * as core from '@janhq/core'
|
||||
```
|
||||
4. In the `onLoad()` method, add your code:
|
||||
|
||||
```ts
|
||||
// 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 {
|
||||
// On extension load
|
||||
onLoad() {
|
||||
core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this));
|
||||
core.events.on(MessageEvent.OnMessageSent, (data) => MyExtension.inference(data, this))
|
||||
}
|
||||
|
||||
// Customized inference logic
|
||||
private static inference(incomingMessage: MessageRequestData) {
|
||||
|
||||
// Prepare customized message content
|
||||
const content: ThreadContent = {
|
||||
type: ContentType.Text,
|
||||
@ -46,16 +47,17 @@ import * as node from "@janhq/core/node";
|
||||
value: "I'm Jan Assistant!",
|
||||
annotations: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Modify message and send out
|
||||
const outGoingMessage: ThreadMessage = {
|
||||
...incomingMessage,
|
||||
content
|
||||
};
|
||||
content,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Build the extension:
|
||||
1. Navigate to the extension directory.
|
||||
2. Install dependencies.
|
||||
@ -66,4 +68,4 @@ import * as node from "@janhq/core/node";
|
||||
```bash
|
||||
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',
|
||||
'@npmcli/arborist',
|
||||
'ulidx',
|
||||
'node-fetch',
|
||||
'fs',
|
||||
'request',
|
||||
'crypto',
|
||||
|
||||
@ -2,7 +2,6 @@ import { openExternalUrl } from './core'
|
||||
import { joinPath } from './core'
|
||||
import { openFileExplorer } from './core'
|
||||
import { getJanDataFolderPath } from './core'
|
||||
import { abortDownload } from './core'
|
||||
import { executeOnMain } from './core'
|
||||
|
||||
describe('test core apis', () => {
|
||||
@ -53,18 +52,6 @@ describe('test core apis', () => {
|
||||
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 () => {
|
||||
const extension = 'testExtension'
|
||||
const method = 'testMethod'
|
||||
|
||||
@ -1,9 +1,4 @@
|
||||
import {
|
||||
DownloadRequest,
|
||||
FileStat,
|
||||
NetworkConfig,
|
||||
SystemInformation,
|
||||
} from '../types'
|
||||
import { SystemInformation } from '../types'
|
||||
|
||||
/**
|
||||
* Execute a extension module function in main process
|
||||
@ -14,42 +9,19 @@ import {
|
||||
* @returns Promise<any>
|
||||
*
|
||||
*/
|
||||
const executeOnMain: (
|
||||
extension: string,
|
||||
method: string,
|
||||
...args: any[]
|
||||
) => Promise<any> = (extension, method, ...args) =>
|
||||
globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args)
|
||||
const executeOnMain: (extension: string, method: string, ...args: any[]) => Promise<any> = (
|
||||
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.
|
||||
*
|
||||
* @returns {Promise<string>} A Promise that resolves with Jan's data folder path.
|
||||
*/
|
||||
const getJanDataFolderPath = (): Promise<string> =>
|
||||
globalThis.core.api?.getJanDataFolderPath()
|
||||
const getJanDataFolderPath = (): Promise<string> => globalThis.core.api?.getJanDataFolderPath()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns {Promise<string>} A promise that resolves the dirname.
|
||||
*/
|
||||
const dirName: (path: string) => Promise<string> = (path) =>
|
||||
globalThis.core.api?.dirName(path)
|
||||
const dirName: (path: string) => Promise<string> = (path) => globalThis.core.api?.dirName(path)
|
||||
|
||||
/**
|
||||
* Retrieve the basename from an url.
|
||||
* @param path - The path to retrieve.
|
||||
* @returns {Promise<string>} A promise that resolves with the basename.
|
||||
*/
|
||||
const baseName: (paths: string) => Promise<string> = (path) =>
|
||||
globalThis.core.api?.baseName(path)
|
||||
const baseName: (paths: string) => Promise<string> = (path) => globalThis.core.api?.baseName(path)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const getResourcePath: () => Promise<string> = () =>
|
||||
globalThis.core.api?.getResourcePath()
|
||||
const getResourcePath: () => Promise<string> = () => globalThis.core.api?.getResourcePath()
|
||||
|
||||
/**
|
||||
* Gets the user's home path.
|
||||
* @returns return user's home path
|
||||
*/
|
||||
const getUserHomePath = (): Promise<string> =>
|
||||
globalThis.core.api?.getUserHomePath()
|
||||
const getUserHomePath = (): Promise<string> => globalThis.core.api?.getUserHomePath()
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (
|
||||
from: string,
|
||||
to: string
|
||||
) => globalThis.core.api?.isSubdirectory(from, to)
|
||||
const isSubdirectory: (from: string, to: string) => Promise<boolean> = (from: string, to: string) =>
|
||||
globalThis.core.api?.isSubdirectory(from, to)
|
||||
|
||||
/**
|
||||
* Get system information
|
||||
@ -159,8 +125,6 @@ export type RegisterExtensionPoint = (
|
||||
*/
|
||||
export {
|
||||
executeOnMain,
|
||||
downloadFile,
|
||||
abortDownload,
|
||||
getJanDataFolderPath,
|
||||
openFileExplorer,
|
||||
getResourcePath,
|
||||
|
||||
@ -39,11 +39,6 @@ describe('BaseExtension', () => {
|
||||
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 () => {
|
||||
await baseExtension.install()
|
||||
// Add your assertions here
|
||||
@ -84,11 +79,6 @@ describe('BaseExtension', () => {
|
||||
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 () => {
|
||||
await baseExtension.install()
|
||||
// Add your assertions here
|
||||
|
||||
@ -12,6 +12,7 @@ export enum ExtensionTypeEnum {
|
||||
SystemMonitoring = 'systemMonitoring',
|
||||
HuggingFace = 'huggingFace',
|
||||
Engine = 'engine',
|
||||
Hardware = 'hardware',
|
||||
}
|
||||
|
||||
export interface ExtensionType {
|
||||
@ -23,17 +24,6 @@ export interface Compatibility {
|
||||
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.
|
||||
* This class should be extended by any class that represents an extension.
|
||||
@ -174,15 +164,6 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
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.
|
||||
*
|
||||
@ -227,7 +208,7 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
|
||||
const settings = await this.getSettings()
|
||||
|
||||
const updatedSettings = settings.map((setting) => {
|
||||
let updatedSettings = settings.map((setting) => {
|
||||
const updatedSetting = componentProps.find(
|
||||
(componentProp) => componentProp.key === setting.key
|
||||
)
|
||||
@ -237,13 +218,20 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
return setting
|
||||
})
|
||||
|
||||
const settingPath = await joinPath([
|
||||
if (!updatedSettings.length) updatedSettings = componentProps as SettingComponentProps[]
|
||||
|
||||
const settingFolder = await joinPath([
|
||||
await getJanDataFolderPath(),
|
||||
this.settingFolderName,
|
||||
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))
|
||||
|
||||
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 { AIEngine } from './AIEngine'
|
||||
import { InferenceEngine } from '../../../types'
|
||||
|
||||
// @ts-ignore
|
||||
class MockAIEngine implements AIEngine {
|
||||
@ -40,4 +41,69 @@ describe('EngineManager', () => {
|
||||
const retrievedEngine = engineManager.get<MockAIEngine>('nonExistentProvider')
|
||||
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', () => {
|
||||
engine.onLoad()
|
||||
expect(events.on).toHaveBeenCalledWith(MessageEvent.OnMessageSent, expect.any(Function))
|
||||
expect(events.on).toHaveBeenCalledWith(InferenceEvent.OnInferenceStopped, expect.any(Function))
|
||||
expect(events.on).toHaveBeenCalledWith(
|
||||
MessageEvent.OnMessageSent,
|
||||
expect.any(Function)
|
||||
)
|
||||
expect(events.on).toHaveBeenCalledWith(
|
||||
InferenceEvent.OnInferenceStopped,
|
||||
expect.any(Function)
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle inference request', async () => {
|
||||
@ -77,7 +83,12 @@ describe('OAIEngine', () => {
|
||||
expect(events.emit).toHaveBeenCalledWith(
|
||||
MessageEvent.OnMessageUpdate,
|
||||
expect.objectContaining({
|
||||
content: [{ type: ContentType.Text, text: { value: 'test response', annotations: [] } }],
|
||||
content: [
|
||||
{
|
||||
type: ContentType.Text,
|
||||
text: { value: 'test response', annotations: [] },
|
||||
},
|
||||
],
|
||||
status: MessageStatus.Ready,
|
||||
})
|
||||
)
|
||||
@ -101,11 +112,10 @@ describe('OAIEngine', () => {
|
||||
|
||||
await engine.inference(data)
|
||||
|
||||
expect(events.emit).toHaveBeenCalledWith(
|
||||
expect(events.emit).toHaveBeenLastCalledWith(
|
||||
MessageEvent.OnMessageUpdate,
|
||||
expect.objectContaining({
|
||||
content: [{ type: ContentType.Text, text: { value: 'test error', annotations: [] } }],
|
||||
status: MessageStatus.Error,
|
||||
status: 'error',
|
||||
error_code: 500,
|
||||
})
|
||||
)
|
||||
|
||||
@ -42,7 +42,9 @@ export abstract class OAIEngine extends AIEngine {
|
||||
*/
|
||||
override 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())
|
||||
}
|
||||
|
||||
@ -128,7 +130,9 @@ export abstract class OAIEngine extends AIEngine {
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
},
|
||||
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)
|
||||
},
|
||||
error: async (err: any) => {
|
||||
@ -141,7 +145,10 @@ export abstract class OAIEngine extends AIEngine {
|
||||
message.content[0] = {
|
||||
type: ContentType.Text,
|
||||
text: {
|
||||
value: err.message,
|
||||
value:
|
||||
typeof message === 'string'
|
||||
? err.message
|
||||
: (JSON.stringify(err.message) ?? err.detail),
|
||||
annotations: [],
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { lastValueFrom, Observable } from 'rxjs'
|
||||
import { requestInference } from './sse'
|
||||
|
||||
import { ReadableStream } from 'stream/web';
|
||||
import { ReadableStream } from 'stream/web'
|
||||
describe('requestInference', () => {
|
||||
it('should send a request to the inference server and return an Observable', () => {
|
||||
// Mock the fetch function
|
||||
const mockFetch: any = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ choices: [{ message: { content: 'Generated response' } }] }),
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [{ message: { content: 'Generated response' } }],
|
||||
}),
|
||||
headers: new Headers(),
|
||||
redirected: false,
|
||||
status: 200,
|
||||
@ -36,7 +39,10 @@ describe('requestInference', () => {
|
||||
const mockFetch: any = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
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(),
|
||||
redirected: false,
|
||||
status: 401,
|
||||
@ -56,69 +62,85 @@ describe('requestInference', () => {
|
||||
|
||||
// Assert the expected behavior
|
||||
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', () => {
|
||||
// Mock the fetch function
|
||||
const mockFetch: any = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () => Promise.resolve({ choices: [{ message: { content: 'Generated response' } }] }),
|
||||
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: 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();
|
||||
}
|
||||
it('should handle a successful response with a transformResponse function', () => {
|
||||
// Mock the fetch function
|
||||
const mockFetch: any = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
choices: [{ message: { content: 'Generated response' } }],
|
||||
}),
|
||||
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');
|
||||
});
|
||||
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: 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) => {
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
let errorCode = ErrorCode.Unknown
|
||||
if (data.error) {
|
||||
errorCode = data.error.code ?? data.error.type ?? ErrorCode.Unknown
|
||||
} else if (response.status === 401) {
|
||||
errorCode = ErrorCode.InvalidApiKey
|
||||
if (response.status === 401) {
|
||||
throw {
|
||||
code: ErrorCode.InvalidApiKey,
|
||||
message: 'Invalid API Key.',
|
||||
}
|
||||
}
|
||||
const error = {
|
||||
message: data.error?.message ?? data.message ?? 'Error occurred.',
|
||||
code: errorCode,
|
||||
let data = await response.json()
|
||||
try {
|
||||
handleError(data)
|
||||
} catch (err) {
|
||||
subscriber.error(err)
|
||||
return
|
||||
}
|
||||
subscriber.error(error)
|
||||
subscriber.complete()
|
||||
return
|
||||
}
|
||||
// There could be overriden stream parameter in the model
|
||||
// that is set in request body (transformed payload)
|
||||
@ -54,9 +53,10 @@ export function requestInference(
|
||||
model.parameters?.stream === false
|
||||
) {
|
||||
const data = await response.json()
|
||||
if (data.error || data.message) {
|
||||
subscriber.error(data.error ?? data)
|
||||
subscriber.complete()
|
||||
try {
|
||||
handleError(data)
|
||||
} catch (err) {
|
||||
subscriber.error(err)
|
||||
return
|
||||
}
|
||||
if (transformResponse) {
|
||||
@ -91,13 +91,10 @@ export function requestInference(
|
||||
const toParse = cachedLines + line
|
||||
if (!line.includes('data: [DONE]')) {
|
||||
const data = JSON.parse(toParse.replace('data: ', ''))
|
||||
if (
|
||||
'error' in data ||
|
||||
'message' in data ||
|
||||
'detail' in data
|
||||
) {
|
||||
subscriber.error(data.error ?? data)
|
||||
subscriber.complete()
|
||||
try {
|
||||
handleError(data)
|
||||
} catch (err) {
|
||||
subscriber.error(err)
|
||||
return
|
||||
}
|
||||
content += data.choices[0]?.delta?.content ?? ''
|
||||
@ -118,3 +115,18 @@ export function requestInference(
|
||||
.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 { InferenceExtension } from './index';
|
||||
import { MonitoringExtension } from './index';
|
||||
import { AssistantExtension } from './index';
|
||||
import { ModelExtension } from './index';
|
||||
import * as Engines from './index';
|
||||
@ -14,10 +13,6 @@ describe('index.ts exports', () => {
|
||||
expect(InferenceExtension).toBeDefined();
|
||||
});
|
||||
|
||||
test('should export MonitoringExtension', () => {
|
||||
expect(MonitoringExtension).toBeDefined();
|
||||
});
|
||||
|
||||
test('should export AssistantExtension', () => {
|
||||
expect(AssistantExtension).toBeDefined();
|
||||
});
|
||||
@ -29,4 +24,4 @@ describe('index.ts exports', () => {
|
||||
test('should export Engines', () => {
|
||||
expect(Engines).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,10 +9,7 @@ export { ConversationalExtension } from './conversational'
|
||||
*/
|
||||
export { InferenceExtension } from './inference'
|
||||
|
||||
/**
|
||||
* Monitoring extension for system monitoring.
|
||||
*/
|
||||
export { MonitoringExtension } from './monitoring'
|
||||
|
||||
|
||||
/**
|
||||
* Assistant extension for managing assistants.
|
||||
@ -33,3 +30,8 @@ export * from './engines'
|
||||
* Engines Management
|
||||
*/
|
||||
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)
|
||||
|
||||
/**
|
||||
* 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) =>
|
||||
globalThis.core.api?.copyFile(src, dest)
|
||||
|
||||
/**
|
||||
* Gets the list of gguf files in a directory
|
||||
*
|
||||
*
|
||||
* @param path - The paths to the file.
|
||||
* @returns {Promise<{any}>} - A promise that resolves with the list of gguf and non-gguf files
|
||||
*/
|
||||
const getGgufFiles: (paths: string[]) => Promise<any> = (
|
||||
paths) => globalThis.core.api?.getGgufFiles(paths)
|
||||
const getGgufFiles: (paths: string[]) => Promise<any> = (paths) =>
|
||||
globalThis.core.api?.getGgufFiles(paths)
|
||||
|
||||
/**
|
||||
* Gets the file's stats.
|
||||
|
||||
@ -1,25 +1,21 @@
|
||||
import {
|
||||
AppRoute,
|
||||
DownloadRoute,
|
||||
ExtensionRoute,
|
||||
FileManagerRoute,
|
||||
FileSystemRoute,
|
||||
} from '../../../types/api'
|
||||
import { Downloader } from '../processors/download'
|
||||
import { FileSystem } from '../processors/fs'
|
||||
import { Extension } from '../processors/extension'
|
||||
import { FSExt } from '../processors/fsExt'
|
||||
import { App } from '../processors/app'
|
||||
|
||||
export class RequestAdapter {
|
||||
downloader: Downloader
|
||||
fileSystem: FileSystem
|
||||
extension: Extension
|
||||
fsExt: FSExt
|
||||
app: App
|
||||
|
||||
constructor(observer?: Function) {
|
||||
this.downloader = new Downloader(observer)
|
||||
this.fileSystem = new FileSystem()
|
||||
this.extension = new Extension()
|
||||
this.fsExt = new FSExt()
|
||||
@ -28,9 +24,7 @@ export class RequestAdapter {
|
||||
|
||||
// TODO: Clearer Factory pattern here
|
||||
process(route: string, ...args: any) {
|
||||
if (route in DownloadRoute) {
|
||||
return this.downloader.process(route, ...args)
|
||||
} else if (route in FileSystemRoute) {
|
||||
if (route in FileSystemRoute) {
|
||||
return this.fileSystem.process(route, ...args)
|
||||
} else if (route in ExtensionRoute) {
|
||||
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 './download'
|
||||
export * from './logger'
|
||||
export * from './module'
|
||||
export * from './path'
|
||||
|
||||
@ -31,6 +31,10 @@ export enum NativeRoute {
|
||||
|
||||
startServer = 'startServer',
|
||||
stopServer = 'stopServer',
|
||||
|
||||
appUpdateDownload = 'appUpdateDownload',
|
||||
|
||||
appToken = 'appToken',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +54,8 @@ export enum AppRoute {
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
onAppUpdateNotAvailable = 'onAppUpdateNotAvailable',
|
||||
onAppUpdateAvailable = 'onAppUpdateAvailable',
|
||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||
@ -61,30 +67,13 @@ export enum AppEvent {
|
||||
onMainViewStateChange = 'onMainViewStateChange',
|
||||
}
|
||||
|
||||
export enum DownloadRoute {
|
||||
abortDownload = 'abortDownload',
|
||||
downloadFile = 'downloadFile',
|
||||
pauseDownload = 'pauseDownload',
|
||||
resumeDownload = 'resumeDownload',
|
||||
getDownloadProgress = 'getDownloadProgress',
|
||||
}
|
||||
|
||||
export enum DownloadEvent {
|
||||
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
||||
onFileDownloadError = 'onFileDownloadError',
|
||||
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
||||
onFileDownloadStopped = 'onFileDownloadStopped',
|
||||
onFileDownloadStarted = 'onFileDownloadStarted',
|
||||
onFileUnzipSuccess = 'onFileUnzipSuccess',
|
||||
}
|
||||
|
||||
export enum LocalImportModelEvent {
|
||||
onLocalImportModelUpdate = 'onLocalImportModelUpdate',
|
||||
onLocalImportModelFailed = 'onLocalImportModelFailed',
|
||||
onLocalImportModelSuccess = 'onLocalImportModelSuccess',
|
||||
onLocalImportModelFinished = 'onLocalImportModelFinished',
|
||||
}
|
||||
|
||||
export enum ExtensionRoute {
|
||||
baseExtensions = 'baseExtensions',
|
||||
getActiveExtensions = 'getActiveExtensions',
|
||||
@ -127,10 +116,6 @@ export type AppEventFunctions = {
|
||||
[K in AppEvent]: ApiFunction
|
||||
}
|
||||
|
||||
export type DownloadRouteFunctions = {
|
||||
[K in DownloadRoute]: ApiFunction
|
||||
}
|
||||
|
||||
export type DownloadEventFunctions = {
|
||||
[K in DownloadEvent]: ApiFunction
|
||||
}
|
||||
@ -150,7 +135,6 @@ export type FileManagerRouteFunctions = {
|
||||
export type APIFunctions = NativeRouteFunctions &
|
||||
AppRouteFunctions &
|
||||
AppEventFunctions &
|
||||
DownloadRouteFunctions &
|
||||
DownloadEventFunctions &
|
||||
ExtensionRouteFunctions &
|
||||
FileSystemRouteFunctions &
|
||||
@ -158,7 +142,6 @@ export type APIFunctions = NativeRouteFunctions &
|
||||
|
||||
export const CoreRoutes = [
|
||||
...Object.values(AppRoute),
|
||||
...Object.values(DownloadRoute),
|
||||
...Object.values(ExtensionRoute),
|
||||
...Object.values(FileSystemRoute),
|
||||
...Object.values(FileManagerRoute),
|
||||
@ -168,7 +151,6 @@ export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)]
|
||||
export const APIEvents = [
|
||||
...Object.values(AppEvent),
|
||||
...Object.values(DownloadEvent),
|
||||
...Object.values(LocalImportModelEvent),
|
||||
]
|
||||
export type PayloadType = {
|
||||
messages: ChatCompletionMessage[]
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export type AppConfiguration = {
|
||||
data_folder: string
|
||||
quick_ask: boolean
|
||||
distinct_id?: string
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ export type EngineMetadata = {
|
||||
template?: string
|
||||
}
|
||||
}
|
||||
explore_models_url?: string
|
||||
}
|
||||
|
||||
export type EngineVariant = {
|
||||
|
||||
@ -16,41 +16,9 @@ export type DownloadState = {
|
||||
|
||||
error?: string
|
||||
extensionId?: string
|
||||
downloadType?: DownloadType | 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 = {
|
||||
elapsed: number
|
||||
remaining: number
|
||||
@ -60,7 +28,6 @@ type DownloadSize = {
|
||||
total: number
|
||||
transferred: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 message from './message';
|
||||
import * as inference from './inference';
|
||||
import * as monitoring from './monitoring';
|
||||
import * as file from './file';
|
||||
import * as config from './config';
|
||||
import * as huggingface from './huggingface';
|
||||
@ -18,7 +17,6 @@ import * as setting from './setting';
|
||||
expect(thread).toBeDefined();
|
||||
expect(message).toBeDefined();
|
||||
expect(inference).toBeDefined();
|
||||
expect(monitoring).toBeDefined();
|
||||
expect(file).toBeDefined();
|
||||
expect(config).toBeDefined();
|
||||
expect(huggingface).toBeDefined();
|
||||
|
||||
@ -3,7 +3,6 @@ export * from './model'
|
||||
export * from './thread'
|
||||
export * from './message'
|
||||
export * from './inference'
|
||||
export * from './monitoring'
|
||||
export * from './file'
|
||||
export * from './config'
|
||||
export * from './huggingface'
|
||||
@ -11,3 +10,4 @@ export * from './miscellaneous'
|
||||
export * from './api'
|
||||
export * from './setting'
|
||||
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 './promptTemplate'
|
||||
export * from './appUpdate'
|
||||
export * from './fileDownloadRequest'
|
||||
export * from './networkConfig'
|
||||
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 = {
|
||||
memAvailable: number
|
||||
}
|
||||
|
||||
export type RunMode = 'cpu' | 'gpu'
|
||||
|
||||
export type GpuSetting = {
|
||||
notify: boolean
|
||||
run_mode: RunMode
|
||||
nvidia_driver: {
|
||||
exist: boolean
|
||||
version: string
|
||||
}
|
||||
cuda: {
|
||||
exist: boolean
|
||||
version: string
|
||||
}
|
||||
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
|
||||
vulkan: boolean
|
||||
cpu?: any
|
||||
}
|
||||
|
||||
export type GpuSettingInfo = {
|
||||
activated: boolean
|
||||
free_vram: number
|
||||
id: string
|
||||
vram: string
|
||||
name: string
|
||||
arch?: string
|
||||
total_vram: number
|
||||
uuid: string
|
||||
version: string
|
||||
additional_information?: GpuAdditionalInformation
|
||||
}
|
||||
|
||||
export type SystemInformation = {
|
||||
@ -42,9 +34,6 @@ export type SupportedPlatform = SupportedPlatformTuple[number]
|
||||
export type OperatingSystemInfo = {
|
||||
platform: SupportedPlatform | 'unknown'
|
||||
arch: string
|
||||
release: string
|
||||
machine: string
|
||||
version: string
|
||||
totalMem: number
|
||||
freeMem: number
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ export type Model = {
|
||||
/**
|
||||
* The model identifier, modern version of id.
|
||||
*/
|
||||
mode?: string
|
||||
model?: string
|
||||
|
||||
/**
|
||||
* Human-readable name that is used for UI.
|
||||
@ -150,6 +150,7 @@ export type ModelSettingParams = {
|
||||
*/
|
||||
export type ModelRuntimeParams = {
|
||||
temperature?: number
|
||||
max_temperature?: number
|
||||
token_limit?: number
|
||||
top_k?: number
|
||||
top_p?: number
|
||||
|
||||
@ -61,6 +61,7 @@ export interface ModelSibling {
|
||||
*/
|
||||
export interface ModelSource {
|
||||
id: string
|
||||
author?: string
|
||||
metadata: Metadata
|
||||
models: ModelSibling[]
|
||||
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 './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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@ const nextConfig = {
|
||||
output: 'export',
|
||||
env: {
|
||||
GTM_ID: process.env.GTM_ID,
|
||||
POSTHOG_KEY: process.env.POSTHOG_KEY,
|
||||
POSTHOG_HOST: process.env.POSTHOG_HOST,
|
||||
},
|
||||
transpilePackages: ['@scalar', 'react-tweet'],
|
||||
images: {
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
"path": "^0.12.7",
|
||||
"plop": "^4.0.1",
|
||||
"plop-helper-date": "^1.0.0",
|
||||
"posthog-js": "^1.194.6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"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"?>
|
||||
<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/about</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>2024-09-09T08:19:45.722Z</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/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/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/qa</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>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>2024-09-09T08:19:45.722Z</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/website-docs</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>2024-09-09T08:19:45.722Z</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/vision</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>2024-09-09T08:19:45.722Z</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/changelog</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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>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>2024-09-09T08:19:45.722Z</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/cortex/architecture</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>2024-09-09T08:19:45.722Z</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/cortex/cli</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>2024-09-09T08:19:45.722Z</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/cortex/cli/kill</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>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>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>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>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>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>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>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>2024-09-09T08:19:45.722Z</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/pull</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>2024-09-09T08:19:45.722Z</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/command-line</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>2024-09-09T08:19:45.722Z</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/cortex-openvino</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>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>2024-09-09T08:19:45.722Z</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/embeddings/overview</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>2024-09-09T08:19:45.722Z</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/fine-tuning</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>2024-09-09T08:19:45.722Z</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/hardware</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>2024-09-09T08:19:45.722Z</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/installation/mac</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>2024-09-09T08:19:45.722Z</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/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/py-library</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>2024-09-09T08:19:45.722Z</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/rag/overview</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>2024-09-09T08:19:45.722Z</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/ts-library</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>2024-09-09T08:19:45.722Z</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/docs</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>2024-09-09T08:19:45.722Z</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/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/docs/data-folder</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>2024-09-09T08:19:45.722Z</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/docs/desktop/mac</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>2024-09-09T08:19:45.722Z</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/docs/extensions</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>2024-09-09T08:19:45.722Z</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/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/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/quickstart</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>2024-09-09T08:19:45.722Z</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/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/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/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/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/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/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/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/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/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/settings</loc><lastmod>2024-09-09T08:19:45.722Z</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/threads</loc><lastmod>2024-09-09T08:19:45.722Z</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/troubleshooting</loc><lastmod>2024-09-09T08:19:45.722Z</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/integrations</loc><lastmod>2024-09-09T08:19:45.722Z</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/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/integrations/messaging/llmcord</loc><lastmod>2024-09-09T08:19:45.722Z</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/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/post/bitdefender</loc><lastmod>2024-09-09T08:19:45.722Z</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/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/privacy</loc><lastmod>2024-09-09T08:19:45.722Z</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</loc><lastmod>2025-03-10T05:06:47.876Z</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>2025-03-10T05:06:47.877Z</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>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>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>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>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>2025-03-10T05:06:47.877Z</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>2025-03-10T05:06:47.877Z</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>2025-03-10T05:06:47.877Z</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>2025-03-10T05:06:47.877Z</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>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>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>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>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>2025-03-10T05:06:47.877Z</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-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-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-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-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-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-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-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-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-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-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-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/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/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/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/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/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/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/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/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/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/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</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>2025-03-10T05:06:47.877Z</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/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</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>2025-03-10T05:06:47.877Z</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/kill</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>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>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>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>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>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>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>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>2025-03-10T05:06:47.877Z</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/cli/pull</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>2025-03-10T05:06:47.877Z</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/command-line</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>2025-03-10T05:06:47.877Z</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/cortex-openvino</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>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>2025-03-10T05:06:47.877Z</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/embeddings/overview</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>2025-03-10T05:06:47.877Z</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/fine-tuning</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>2025-03-10T05:06:47.877Z</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/hardware</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>2025-03-10T05:06:47.877Z</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/installation/mac</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>2025-03-10T05:06:47.877Z</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/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/cortex/py-library</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>2025-03-10T05:06:47.877Z</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/cortex/rag/overview</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>2025-03-10T05:06:47.877Z</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/cortex/ts-library</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>2025-03-10T05:06:47.877Z</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</loc><lastmod>2025-03-10T05:06:47.877Z</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/assistants</loc><lastmod>2025-03-10T05:06:47.877Z</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/data-folder</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>2025-03-10T05:06:47.877Z</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/desktop/mac</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>2025-03-10T05:06:47.877Z</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/extensions</loc><lastmod>2025-03-10T05:06:47.877Z</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/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/install-engines</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>2025-03-10T05:06:47.877Z</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/models</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>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>2025-03-10T05:06:47.877Z</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/privacy-policy</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>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>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>2025-03-10T05:06:47.877Z</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/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/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/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/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/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/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/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/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/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>
|
||||
@ -27,7 +27,7 @@ export const APIReference = () => {
|
||||
<ApiReferenceReact
|
||||
configuration={{
|
||||
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',
|
||||
hideModels: true,
|
||||
|
||||
@ -57,7 +57,7 @@ const Changelog = () => {
|
||||
<p className="text-base mt-2 leading-relaxed">
|
||||
Latest release updates from the Jan team. Check out our
|
||||
<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"
|
||||
>
|
||||
Roadmap
|
||||
@ -150,7 +150,7 @@ const Changelog = () => {
|
||||
|
||||
<div className="text-center">
|
||||
<Link
|
||||
href="https://github.com/janhq/jan/releases"
|
||||
href="https://github.com/menloresearch/jan/releases"
|
||||
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"
|
||||
>
|
||||
|
||||
@ -86,7 +86,7 @@ export default function CardDownload({ lastRelease }: Props) {
|
||||
.replace('{tag}', tag)
|
||||
return {
|
||||
...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)
|
||||
return {
|
||||
...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)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ThemeImage from '@/components/ThemeImage'
|
||||
import { AiOutlineGithub } from 'react-icons/ai'
|
||||
import { RiTwitterXFill } from 'react-icons/ri'
|
||||
@ -7,6 +7,7 @@ import { BiLogoDiscordAlt } from 'react-icons/bi'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import LogoMark from '@/components/LogoMark'
|
||||
import { FaLinkedin } from 'react-icons/fa'
|
||||
import posthog from 'posthog-js'
|
||||
|
||||
const socials = [
|
||||
{
|
||||
@ -25,7 +26,7 @@ const socials = [
|
||||
icon: (
|
||||
<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" />,
|
||||
@ -61,7 +62,7 @@ const menus = [
|
||||
child: [
|
||||
{
|
||||
menu: 'Github',
|
||||
path: 'https://github.com/janhq/jan',
|
||||
path: 'https://github.com/menloresearch/jan',
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
@ -94,7 +95,7 @@ const menus = [
|
||||
},
|
||||
{
|
||||
menu: 'Careers',
|
||||
path: 'https://homebrew.bamboohr.com/careers',
|
||||
path: 'https://menlo.bamboohr.com/careers',
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
@ -104,6 +105,19 @@ const menus = [
|
||||
const getCurrentYear = new Date().getFullYear()
|
||||
|
||||
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({
|
||||
defaultValues: {
|
||||
email: '',
|
||||
@ -237,14 +251,14 @@ export default function Footer() {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<span>©{getCurrentYear} Homebrew Computer Company</span>
|
||||
<span>©{getCurrentYear} Menlo Research</span>
|
||||
<ThemeImage
|
||||
source={{
|
||||
light: '/assets/images/general/homebrew-dark.svg',
|
||||
dark: '/assets/images/general/homebrew-white.svg',
|
||||
light: '/assets/images/general/menlo.svg',
|
||||
dark: '/assets/images/general/menlo.svg',
|
||||
}}
|
||||
alt="App screenshots"
|
||||
width={140}
|
||||
width={80}
|
||||
height={200}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -23,7 +23,7 @@ const BuiltWithLove = () => {
|
||||
</div>
|
||||
<div className="flex flex-col lg:flex-row gap-8 mt-8 items-center justify-center">
|
||||
<a
|
||||
href="https://github.com/janhq/jan"
|
||||
href="https://github.com/menloresearch/jan"
|
||||
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 "
|
||||
>
|
||||
|
||||
@ -44,7 +44,7 @@ const Hero = () => {
|
||||
<div className="mt-10 text-center">
|
||||
<div>
|
||||
<Link
|
||||
href="https://github.com/janhq/jan/releases"
|
||||
href="https://github.com/menloresearch/jan/releases"
|
||||
target="_blank"
|
||||
className="hidden lg:inline-block"
|
||||
>
|
||||
|
||||
@ -3,7 +3,6 @@ import '@code-hike/mdx/styles.css'
|
||||
import { Fragment } from "react"
|
||||
import Script from "next/script"
|
||||
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<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:
|
||||
|
||||
- [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)
|
||||
- [Nitro: run Local AI](https://github.com/janhq/nitro) (AGPLv3)
|
||||
- [Nitro: run Local AI](https://github.com/menloresearch/nitro) (AGPLv3)
|
||||
|
||||
## Build in Public
|
||||
|
||||
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 Roadmap](https://github.com/orgs/janhq/projects/5/views/29)
|
||||
- [Jan's Kanban](https://github.com/orgs/menloresearch/projects/5)
|
||||
- [Jan's Roadmap](https://github.com/orgs/menloresearch/projects/5/views/29)
|
||||
|
||||
## 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/).
|
||||
|
||||
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
|
||||
|
||||
- [Requirements](https://github.com/janhq/jan?tab=readme-ov-file#requirements-for-running-jan)
|
||||
- [Setting up local env](https://github.com/janhq/jan?tab=readme-ov-file#contributing)
|
||||
- [Requirements](https://github.com/menloresearch/jan?tab=readme-ov-file#requirements-for-running-jan)
|
||||
- [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
|
||||
|
||||
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`.
|
||||
|
||||
@ -30,37 +30,37 @@ As much as possible, everyone owns their respective `epics` and `tasks`.
|
||||
|
||||
## Quicklinks
|
||||
|
||||
- [High-level roadmap](https://github.com/orgs/janhq/projects/5/views/16): view used at at strategic level, for team wide alignment. Start & end dates reflect engineering implementation cycles. Typically product & design work preceeds these timelines.
|
||||
- [Standup Kanban](https://github.com/orgs/janhq/projects/5/views/25): view used during daily standup. Sprints should be up to date.
|
||||
- [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/menloresearch/projects/5/views/25): view used during daily standup. Sprints should be up to date.
|
||||
|
||||
## 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
|
||||
- Example label: `roadmap: Jan has Mobile`
|
||||
- `Roadmaps` contain `epics`
|
||||
|
||||
[`Epics`](https://github.com/janhq/jan/issues?q=is%3Aissue+is%3Aopen+label%3A%22type%3A+epic%22)
|
||||
[`Epics`](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` contain `tasks`
|
||||
- `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` span ~2 weeks and have deadlines
|
||||
- `Milestones` usually fit within 2-week sprint cycles
|
||||
|
||||
[`Tasks`](https://github.com/janhq/jan/issues)
|
||||
[`Tasks`](https://github.com/menloresearch/jan/issues)
|
||||
|
||||
- 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 are usually named per [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary)
|
||||
- Tasks should always have 1 owner
|
||||
|
||||
We aim to always 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
|
||||
|
||||
@ -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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
---
|
||||
title: Homebrew Computer Company
|
||||
description: We are Homebrew Computer Company, the creators and maintainers of Jan, Cortex and other tools.
|
||||
title: Menlo Research
|
||||
description: We are Menlo Research, the creators and maintainers of Jan, Cortex and other tools.
|
||||
keywords:
|
||||
[
|
||||
Homebrew Computer Company,
|
||||
Menlo Research,
|
||||
Jan,
|
||||
local AI,
|
||||
open-source alternative to chatgpt,
|
||||
@ -22,7 +22,7 @@ keywords:
|
||||
|
||||
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)_
|
||||
@ -36,7 +36,7 @@ We're a team of AI researchers and engineers. We are the creators and lead maint
|
||||
- More to come!
|
||||
|
||||
<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>
|
||||
|
||||
### 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 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
|
||||
|
||||
@ -93,7 +93,7 @@ We are inspired by and actively try to emulate the paths of companies we admire
|
||||
- [Obsidian](https://obsidian.md/)
|
||||
- [Discourse](https://www.discourse.org/about)
|
||||
- [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/)
|
||||
- [Lago](https://www.getlago.com/blog/open-source-licensing-and-why-lago-chose-agplv3)
|
||||
- [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 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="🌏">
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
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!)
|
||||
- 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.
|
||||
|
||||
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
|
||||
- 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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