Merge branch 'dev' into chore/blogpost-finetuning
4
.github/scripts/auto-sign.sh
vendored
@ -7,6 +7,6 @@ if [[ -z "$APP_PATH" ]] || [[ -z "$DEVELOPER_ID" ]]; then
|
||||
fi
|
||||
|
||||
# If both variables are set, execute the following commands
|
||||
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
find "$APP_PATH" \( -type f -perm +111 -o -name "*.node" \) -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
|
||||
find "$APP_PATH" -type f -name "*.o" -exec codesign -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
find "$APP_PATH" -type f -name "*.o" -exec codesign --force -s "$DEVELOPER_ID" --options=runtime {} \;
|
||||
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
- name: Delete object older than 10 days
|
||||
run: |
|
||||
# Get the list of objects in the 'latest' folder
|
||||
OBJECTS=$(aws s3api list-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --query 'Contents[?LastModified<`'$(date -d "$current_date -10 days" -u +"%Y-%m-%dT%H:%M:%SZ")'`].{Key: Key}' --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | jq -c .)
|
||||
OBJECTS=$(aws s3api list-objects --bucket ${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }} --prefix "latest/" --query 'Contents[?LastModified<`'$(date -d "$current_date -10 days" -u +"%Y-%m-%dT%H:%M:%SZ")'`].{Key: Key}' --endpoint-url https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com | jq -c .)
|
||||
|
||||
# Create a JSON file for the delete operation
|
||||
echo "{\"Objects\": $OBJECTS, \"Quiet\": false}" > delete.json
|
||||
|
||||
@ -22,6 +22,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- release/**
|
||||
paths:
|
||||
- "electron/**"
|
||||
- .github/workflows/jan-electron-linter-and-test.yml
|
||||
@ -66,17 +67,51 @@ jobs:
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
||||
|
||||
test-on-windows:
|
||||
if: github.event_name == 'push'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
||||
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
Remove-Item -Path .\* -Force -Recurse
|
||||
Remove-Item -Path "\\?\$(Get-Location)\*" -Force -Recurse
|
||||
$path = "$Env:APPDATA\jan"
|
||||
if (Test-Path $path) {
|
||||
Remove-Item $path -Recurse -Force
|
||||
Remove-Item "\\?\$path" -Recurse -Force
|
||||
} else {
|
||||
Write-Output "Folder does not exist."
|
||||
}
|
||||
- name: Getting the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Installing node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
# Clean cache, continue on error
|
||||
- name: "Cleanup cache"
|
||||
shell: powershell
|
||||
continue-on-error: true
|
||||
run: |
|
||||
make clean
|
||||
|
||||
- name: Linter and test
|
||||
shell: powershell
|
||||
run: |
|
||||
make test
|
||||
test-on-windows-pr:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: windows-desktop-default-windows-security
|
||||
steps:
|
||||
- name: Clean workspace
|
||||
run: |
|
||||
Remove-Item -Path "\\?\$(Get-Location)\*" -Force -Recurse
|
||||
$path = "$Env:APPDATA\jan"
|
||||
if (Test-Path $path) {
|
||||
Remove-Item "\\?\$path" -Recurse -Force
|
||||
} else {
|
||||
Write-Output "Folder does not exist."
|
||||
}
|
||||
|
||||
@ -78,6 +78,10 @@ jobs:
|
||||
|
||||
jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
cat electron/package.json
|
||||
|
||||
- name: Update app version base on tag
|
||||
@ -91,6 +95,9 @@ jobs:
|
||||
mv /tmp/package.json electron/package.json
|
||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
cat electron/package.json
|
||||
env:
|
||||
VERSION_TAG: ${{ inputs.new_version }}
|
||||
|
||||
|
||||
@ -72,6 +72,10 @@ jobs:
|
||||
|
||||
jq '.build.publish = [{"provider": "generic", "url": "${{ secrets.CLOUDFLARE_R2_PUBLIC_URL }}", "channel": "latest"}, {"provider": "s3", "bucket": "${{ secrets.CLOUDFLARE_R2_BUCKET_NAME }}", "region": "auto", "endpoint": "https://${{ secrets.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com", "path": "${{ inputs.cloudflare_r2_path }}", "channel": "latest"}]' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
|
||||
cat electron/package.json
|
||||
|
||||
- name: Update app version base on tag
|
||||
@ -85,6 +89,9 @@ jobs:
|
||||
mv /tmp/package.json electron/package.json
|
||||
jq --arg version "${VERSION_TAG#v}" '.version = $version' web/package.json > /tmp/package.json
|
||||
mv /tmp/package.json web/package.json
|
||||
jq --arg teamid "${{ secrets.APPLE_TEAM_ID }}" '.build.mac.notarize.teamId = $teamid' electron/package.json > /tmp/package.json
|
||||
mv /tmp/package.json electron/package.json
|
||||
cat electron/package.json
|
||||
env:
|
||||
VERSION_TAG: ${{ inputs.new_version }}
|
||||
|
||||
|
||||
25
.gitignore
vendored
@ -14,6 +14,7 @@ electron/renderer
|
||||
electron/models
|
||||
electron/docs
|
||||
electron/engines
|
||||
electron/playwright-report
|
||||
server/pre-install
|
||||
package-lock.json
|
||||
|
||||
@ -21,13 +22,17 @@ package-lock.json
|
||||
core/lib/**
|
||||
|
||||
# Nitro binary files
|
||||
extensions/inference-nitro-extension/bin/*/nitro
|
||||
extensions/inference-nitro-extension/bin/*/*.metal
|
||||
extensions/inference-nitro-extension/bin/*/*.exe
|
||||
extensions/inference-nitro-extension/bin/*/*.dll
|
||||
extensions/inference-nitro-extension/bin/*/*.exp
|
||||
extensions/inference-nitro-extension/bin/*/*.lib
|
||||
extensions/inference-nitro-extension/bin/saved-*
|
||||
extensions/inference-nitro-extension/bin/*.tar.gz
|
||||
extensions/inference-nitro-extension/bin/vulkaninfoSDK.exe
|
||||
extensions/inference-nitro-extension/bin/vulkaninfo
|
||||
extensions/*-extension/bin/*/nitro
|
||||
extensions/*-extension/bin/*/*.metal
|
||||
extensions/*-extension/bin/*/*.exe
|
||||
extensions/*-extension/bin/*/*.dll
|
||||
extensions/*-extension/bin/*/*.exp
|
||||
extensions/*-extension/bin/*/*.lib
|
||||
extensions/*-extension/bin/saved-*
|
||||
extensions/*-extension/bin/*.tar.gz
|
||||
extensions/*-extension/bin/vulkaninfoSDK.exe
|
||||
extensions/*-extension/bin/vulkaninfo
|
||||
|
||||
|
||||
# Turborepo
|
||||
.turbo
|
||||
5
Makefile
@ -53,15 +53,17 @@ build: check-file-counts
|
||||
clean:
|
||||
ifeq ($(OS),Windows_NT)
|
||||
powershell -Command "Get-ChildItem -Path . -Include node_modules, .next, dist, build, out -Recurse -Directory | Remove-Item -Recurse -Force"
|
||||
powershell -Command "Get-ChildItem -Path . -Include package-lock.json -Recurse -File | Remove-Item -Recurse -Force"
|
||||
powershell -Command "Remove-Item -Recurse -Force ./pre-install/*.tgz"
|
||||
powershell -Command "Remove-Item -Recurse -Force ./electron/pre-install/*.tgz"
|
||||
rmdir /s /q "%USERPROFILE%\jan\extensions"
|
||||
powershell -Command "if (Test-Path \"$($env:USERPROFILE)\jan\extensions\") { Remove-Item -Path \"$($env:USERPROFILE)\jan\extensions\" -Recurse -Force }"
|
||||
else ifeq ($(shell uname -s),Linux)
|
||||
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
|
||||
find . -name ".next" -type d -exec rm -rf '{}' +
|
||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||
find . -name "build" -type d -exec rm -rf '{}' +
|
||||
find . -name "out" -type d -exec rm -rf '{}' +
|
||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||
rm -rf ./pre-install/*.tgz
|
||||
rm -rf ./electron/pre-install/*.tgz
|
||||
rm -rf "~/jan/extensions"
|
||||
@ -72,6 +74,7 @@ else
|
||||
find . -name "dist" -type d -exec rm -rf '{}' +
|
||||
find . -name "build" -type d -exec rm -rf '{}' +
|
||||
find . -name "out" -type d -exec rm -rf '{}' +
|
||||
find . -name "packake-lock.json" -type f -exec rm -rf '{}' +
|
||||
rm -rf ./pre-install/*.tgz
|
||||
rm -rf ./electron/pre-install/*.tgz
|
||||
rm -rf ~/jan/extensions
|
||||
|
||||
30
README.md
@ -43,31 +43,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Stable (Recommended)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-win-x64-0.4.7.exe'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-win-x64-0.4.9.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-x64-0.4.7.dmg'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-x64-0.4.9.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-mac-arm64-0.4.7.dmg'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-mac-arm64-0.4.9.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-amd64-0.4.7.deb'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-linux-amd64-0.4.9.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.7/jan-linux-x86_64-0.4.7.AppImage'>
|
||||
<a href='https://github.com/janhq/jan/releases/download/v0.4.9/jan-linux-x86_64-0.4.9.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
@ -76,31 +76,31 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
||||
<tr style="text-align:center">
|
||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.7-286.exe'>
|
||||
<a href='https://delta.jan.ai/latest/jan-win-x64-0.4.9-337.exe'>
|
||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||
<b>jan.exe</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.7-286.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-x64-0.4.9-337.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>Intel</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.7-286.dmg'>
|
||||
<a href='https://delta.jan.ai/latest/jan-mac-arm64-0.4.9-337.dmg'>
|
||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||
<b>M1/M2</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.7-286.deb'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-amd64-0.4.9-337.deb'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.deb</b>
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align:center">
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.7-286.AppImage'>
|
||||
<a href='https://delta.jan.ai/latest/jan-linux-x86_64-0.4.9-337.AppImage'>
|
||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||
<b>jan.AppImage</b>
|
||||
</a>
|
||||
@ -209,6 +209,12 @@ Contributions are welcome! Please read the [CONTRIBUTING.md](CONTRIBUTING.md) fi
|
||||
|
||||
This will start the development server and open the desktop app.
|
||||
|
||||
3. (Optional) **Run the API server without frontend**
|
||||
|
||||
```bash
|
||||
yarn dev:server
|
||||
```
|
||||
|
||||
### For production build
|
||||
|
||||
```bash
|
||||
@ -304,7 +310,7 @@ This will build the app MacOS m1/m2 for production (with code signing already do
|
||||
|
||||
```bash
|
||||
# GPU mode with default file system
|
||||
docker compose --profile gpu up -d
|
||||
docker compose --profile gpu-fs up -d
|
||||
|
||||
# GPU mode with S3 file system
|
||||
docker compose --profile gpu-s3fs up -d
|
||||
@ -319,7 +325,9 @@ This will start the web server and you can access Jan at `http://localhost:3000`
|
||||
Jan builds on top of other open-source projects:
|
||||
|
||||
- [llama.cpp](https://github.com/ggerganov/llama.cpp)
|
||||
- [LangChain](https://github.com/langchain-ai)
|
||||
- [TensorRT](https://github.com/NVIDIA/TensorRT)
|
||||
- [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM)
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
@ -8,8 +8,8 @@
|
||||
],
|
||||
"homepage": "https://jan.ai",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "dist/core.umd.js",
|
||||
"module": "dist/core.es5.js",
|
||||
"main": "dist/core.es5.js",
|
||||
"module": "dist/core.cjs.js",
|
||||
"typings": "dist/types/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
@ -17,8 +17,7 @@
|
||||
],
|
||||
"author": "Jan <service@jan.ai>",
|
||||
"exports": {
|
||||
".": "./dist/core.umd.js",
|
||||
"./sdk": "./dist/core.umd.js",
|
||||
".": "./dist/core.es5.js",
|
||||
"./node": "./dist/node/index.cjs.js"
|
||||
},
|
||||
"typesVersions": {
|
||||
@ -27,10 +26,6 @@
|
||||
"./dist/core.es5.js.map",
|
||||
"./dist/types/index.d.ts"
|
||||
],
|
||||
"sdk": [
|
||||
"./dist/core.es5.js.map",
|
||||
"./dist/types/index.d.ts"
|
||||
],
|
||||
"node": [
|
||||
"./dist/node/index.cjs.js.map",
|
||||
"./dist/types/node/index.d.ts"
|
||||
@ -38,26 +33,32 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
|
||||
"lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'",
|
||||
"test": "jest",
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "tsc --module commonjs && rollup -c rollup.config.ts",
|
||||
"start": "rollup -c rollup.config.ts -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^25.4.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^12.0.2",
|
||||
"eslint-plugin-jest": "^23.8.2",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.11.4",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"jest": "^29.7.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.38.5",
|
||||
"rollup-plugin-commonjs": "^9.1.8",
|
||||
"rollup-plugin-json": "^3.1.0",
|
||||
"rollup-plugin-node-resolve": "^5.2.0",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-typescript2": "^0.36.0",
|
||||
"ts-jest": "^26.1.1",
|
||||
"ts-jest": "^29.1.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"rimraf": "^3.0.2"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"rxjs": "^7.8.1",
|
||||
"ulidx": "^2.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,17 +3,16 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
import sourceMaps from 'rollup-plugin-sourcemaps'
|
||||
import typescript from 'rollup-plugin-typescript2'
|
||||
import json from 'rollup-plugin-json'
|
||||
import replace from '@rollup/plugin-replace'
|
||||
|
||||
const pkg = require('./package.json')
|
||||
|
||||
const libraryName = 'core'
|
||||
|
||||
export default [
|
||||
{
|
||||
input: `src/index.ts`,
|
||||
output: [
|
||||
{ file: pkg.main, name: libraryName, format: 'umd', sourcemap: true },
|
||||
{ file: pkg.module, format: 'es', sourcemap: true },
|
||||
// { file: pkg.main, name: libraryName, format: 'umd', sourcemap: true },
|
||||
{ file: pkg.main, format: 'es', sourcemap: true },
|
||||
],
|
||||
// Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash')
|
||||
external: ['path'],
|
||||
@ -30,7 +29,13 @@ export default [
|
||||
// Allow node_modules resolution, so you can use 'external' to control
|
||||
// which external modules to include in the bundle
|
||||
// https://github.com/rollup/rollup-plugin-node-resolve#usage
|
||||
resolve(),
|
||||
replace({
|
||||
'node:crypto': 'crypto',
|
||||
'delimiters': ['"', '"'],
|
||||
}),
|
||||
resolve({
|
||||
browser: true,
|
||||
}),
|
||||
|
||||
// Resolve source maps to the original source
|
||||
sourceMaps(),
|
||||
@ -46,7 +51,7 @@ export default [
|
||||
'pacote',
|
||||
'@types/pacote',
|
||||
'@npmcli/arborist',
|
||||
'ulid',
|
||||
'ulidx',
|
||||
'node-fetch',
|
||||
'fs',
|
||||
'request',
|
||||
|
||||
@ -7,7 +7,16 @@ export enum NativeRoute {
|
||||
openAppDirectory = 'openAppDirectory',
|
||||
openFileExplore = 'openFileExplorer',
|
||||
selectDirectory = 'selectDirectory',
|
||||
selectModelFiles = 'selectModelFiles',
|
||||
relaunch = 'relaunch',
|
||||
|
||||
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||
sendQuickAskInput = 'sendQuickAskInput',
|
||||
|
||||
hideMainWindow = 'hideMainWindow',
|
||||
showMainWindow = 'showMainWindow',
|
||||
|
||||
quickAskSizeUpdated = 'quickAskSizeUpdated',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -24,12 +33,17 @@ export enum AppRoute {
|
||||
stopServer = 'stopServer',
|
||||
log = 'log',
|
||||
logServer = 'logServer',
|
||||
systemInformation = 'systemInformation',
|
||||
showToast = 'showToast',
|
||||
}
|
||||
|
||||
export enum AppEvent {
|
||||
onAppUpdateDownloadUpdate = 'onAppUpdateDownloadUpdate',
|
||||
onAppUpdateDownloadError = 'onAppUpdateDownloadError',
|
||||
onAppUpdateDownloadSuccess = 'onAppUpdateDownloadSuccess',
|
||||
|
||||
onUserSubmitQuickAsk = 'onUserSubmitQuickAsk',
|
||||
onSelectedText = 'onSelectedText',
|
||||
}
|
||||
|
||||
export enum DownloadRoute {
|
||||
@ -44,6 +58,14 @@ export enum DownloadEvent {
|
||||
onFileDownloadUpdate = 'onFileDownloadUpdate',
|
||||
onFileDownloadError = 'onFileDownloadError',
|
||||
onFileDownloadSuccess = 'onFileDownloadSuccess',
|
||||
onFileUnzipSuccess = 'onFileUnzipSuccess',
|
||||
}
|
||||
|
||||
export enum LocalImportModelEvent {
|
||||
onLocalImportModelUpdate = 'onLocalImportModelUpdate',
|
||||
onLocalImportModelFailed = 'onLocalImportModelFailed',
|
||||
onLocalImportModelSuccess = 'onLocalImportModelSuccess',
|
||||
onLocalImportModelFinished = 'onLocalImportModelFinished',
|
||||
}
|
||||
|
||||
export enum ExtensionRoute {
|
||||
@ -67,11 +89,14 @@ export enum FileSystemRoute {
|
||||
}
|
||||
export enum FileManagerRoute {
|
||||
syncFile = 'syncFile',
|
||||
copyFile = 'copyFile',
|
||||
getJanDataFolderPath = 'getJanDataFolderPath',
|
||||
getResourcePath = 'getResourcePath',
|
||||
getUserHomePath = 'getUserHomePath',
|
||||
fileStat = 'fileStat',
|
||||
writeBlob = 'writeBlob',
|
||||
mkdir = 'mkdir',
|
||||
rm = 'rm',
|
||||
}
|
||||
|
||||
export type ApiFunction = (...args: any[]) => any
|
||||
@ -126,4 +151,8 @@ export const CoreRoutes = [
|
||||
]
|
||||
|
||||
export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)]
|
||||
export const APIEvents = [...Object.values(AppEvent), ...Object.values(DownloadEvent)]
|
||||
export const APIEvents = [
|
||||
...Object.values(AppEvent),
|
||||
...Object.values(DownloadEvent),
|
||||
...Object.values(LocalImportModelEvent),
|
||||
]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { FileStat } from './types'
|
||||
import { DownloadRequest, FileStat, NetworkConfig, SystemInformation } from './types'
|
||||
|
||||
/**
|
||||
* Execute a extension module function in main process
|
||||
@ -13,22 +13,20 @@ const executeOnMain: (extension: string, method: string, ...args: any[]) => Prom
|
||||
extension,
|
||||
method,
|
||||
...args
|
||||
) => global.core?.api?.invokeExtensionFunc(extension, method, ...args)
|
||||
) => globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args)
|
||||
|
||||
/**
|
||||
* Downloads a file from a URL and saves it to the local file system.
|
||||
* @param {string} url - The URL of the file to download.
|
||||
* @param {string} fileName - The name to use for the downloaded file.
|
||||
* @param {object} network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||
*
|
||||
* @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: (
|
||||
url: string,
|
||||
fileName: string,
|
||||
network?: { proxy?: string; ignoreSSL?: boolean }
|
||||
) => Promise<any> = (url, fileName, network) => {
|
||||
return global.core?.api?.downloadFile(url, fileName, network)
|
||||
}
|
||||
const downloadFile: (downloadRequest: DownloadRequest, network?: NetworkConfig) => Promise<any> = (
|
||||
downloadRequest,
|
||||
network
|
||||
) => globalThis.core?.api?.downloadFile(downloadRequest, network)
|
||||
|
||||
/**
|
||||
* Aborts the download of a specific file.
|
||||
@ -36,14 +34,14 @@ const downloadFile: (
|
||||
* @returns {Promise<any>} A promise that resolves when the download has been aborted.
|
||||
*/
|
||||
const abortDownload: (fileName: string) => Promise<any> = (fileName) =>
|
||||
global.core.api?.abortDownload(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> => global.core.api?.getJanDataFolderPath()
|
||||
const getJanDataFolderPath = (): Promise<string> => globalThis.core.api?.getJanDataFolderPath()
|
||||
|
||||
/**
|
||||
* Opens the file explorer at a specific path.
|
||||
@ -51,21 +49,22 @@ const getJanDataFolderPath = (): Promise<string> => global.core.api?.getJanDataF
|
||||
* @returns {Promise<any>} A promise that resolves when the file explorer is opened.
|
||||
*/
|
||||
const openFileExplorer: (path: string) => Promise<any> = (path) =>
|
||||
global.core.api?.openFileExplorer(path)
|
||||
globalThis.core.api?.openFileExplorer(path)
|
||||
|
||||
/**
|
||||
* Joins multiple paths together.
|
||||
* @param paths - The paths to join.
|
||||
* @returns {Promise<string>} A promise that resolves with the joined path.
|
||||
*/
|
||||
const joinPath: (paths: string[]) => Promise<string> = (paths) => global.core.api?.joinPath(paths)
|
||||
const joinPath: (paths: string[]) => Promise<string> = (paths) =>
|
||||
globalThis.core.api?.joinPath(paths)
|
||||
|
||||
/**
|
||||
* Retrive 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) => global.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.
|
||||
@ -74,20 +73,20 @@ const baseName: (paths: string[]) => Promise<string> = (path) => global.core.api
|
||||
* @returns {Promise<any>} - A promise that resolves when the URL has been successfully opened.
|
||||
*/
|
||||
const openExternalUrl: (url: string) => Promise<any> = (url) =>
|
||||
global.core.api?.openExternalUrl(url)
|
||||
globalThis.core.api?.openExternalUrl(url)
|
||||
|
||||
/**
|
||||
* Gets the resource path of the application.
|
||||
*
|
||||
* @returns {Promise<string>} - A promise that resolves with the resource path.
|
||||
*/
|
||||
const getResourcePath: () => Promise<string> = () => global.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> => global.core.api?.getUserHomePath()
|
||||
const getUserHomePath = (): Promise<string> => globalThis.core.api?.getUserHomePath()
|
||||
|
||||
/**
|
||||
* Log to file from browser processes.
|
||||
@ -95,7 +94,7 @@ const getUserHomePath = (): Promise<string> => global.core.api?.getUserHomePath(
|
||||
* @param message - Message to log.
|
||||
*/
|
||||
const log: (message: string, fileName?: string) => void = (message, fileName) =>
|
||||
global.core.api?.log(message, fileName)
|
||||
globalThis.core.api?.log(message, fileName)
|
||||
|
||||
/**
|
||||
* Check whether the path is a subdirectory of another path.
|
||||
@ -106,8 +105,23 @@ 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) =>
|
||||
global.core.api?.isSubdirectory(from, to)
|
||||
globalThis.core.api?.isSubdirectory(from, to)
|
||||
|
||||
/**
|
||||
* Get system information
|
||||
* @returns {Promise<any>} - A promise that resolves with the system information.
|
||||
*/
|
||||
const systemInformation: () => Promise<SystemInformation> = () =>
|
||||
globalThis.core.api?.systemInformation()
|
||||
|
||||
/**
|
||||
* Show toast message from browser processes.
|
||||
* @param title
|
||||
* @param message
|
||||
* @returns
|
||||
*/
|
||||
const showToast: (title: string, message: string) => void = (title, message) =>
|
||||
globalThis.core.api?.showToast(title, message)
|
||||
/**
|
||||
* Register extension point function type definition
|
||||
*/
|
||||
@ -134,5 +148,7 @@ export {
|
||||
log,
|
||||
isSubdirectory,
|
||||
getUserHomePath,
|
||||
systemInformation,
|
||||
showToast,
|
||||
FileStat,
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* @param handler The handler function to call when the event is observed.
|
||||
*/
|
||||
const on: (eventName: string, handler: Function) => void = (eventName, handler) => {
|
||||
global.core?.events?.on(eventName, handler)
|
||||
globalThis.core?.events?.on(eventName, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -15,7 +15,7 @@ const on: (eventName: string, handler: Function) => void = (eventName, handler)
|
||||
* @param handler The handler function to call when the event is observed.
|
||||
*/
|
||||
const off: (eventName: string, handler: Function) => void = (eventName, handler) => {
|
||||
global.core?.events?.off(eventName, handler)
|
||||
globalThis.core?.events?.off(eventName, handler)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +25,7 @@ const off: (eventName: string, handler: Function) => void = (eventName, handler)
|
||||
* @param object The object to pass to the event callback.
|
||||
*/
|
||||
const emit: (eventName: string, object: any) => void = (eventName, object) => {
|
||||
global.core?.events?.emit(eventName, object)
|
||||
globalThis.core?.events?.emit(eventName, object)
|
||||
}
|
||||
|
||||
export const events = {
|
||||
|
||||
@ -10,6 +10,23 @@ export enum ExtensionTypeEnum {
|
||||
export interface ExtensionType {
|
||||
type(): ExtensionTypeEnum | undefined
|
||||
}
|
||||
|
||||
export interface Compatibility {
|
||||
platform: string[]
|
||||
version: string
|
||||
}
|
||||
|
||||
const ALL_INSTALLATION_STATE = [
|
||||
'NotRequired', // not required.
|
||||
'Installed', // require and installed. Good to go.
|
||||
'Updatable', // require and installed but need to be updated.
|
||||
'NotInstalled', // require to be installed.
|
||||
'Corrupted', // require but corrupted. Need to redownload.
|
||||
] 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.
|
||||
@ -33,4 +50,39 @@ export abstract class BaseExtension implements ExtensionType {
|
||||
* Any cleanup logic for the extension should be put here.
|
||||
*/
|
||||
abstract onUnload(): void
|
||||
|
||||
/**
|
||||
* The compatibility of the extension.
|
||||
* This is used to check if the extension is compatible with the current environment.
|
||||
* @property {Array} platform
|
||||
*/
|
||||
compatibility(): Compatibility | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the extension is updatable.
|
||||
*/
|
||||
updatable(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
async install(...args): Promise<void> {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
62
core/src/extensions/ai-engines/AIEngine.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { getJanDataFolderPath, joinPath } from '../../core'
|
||||
import { events } from '../../events'
|
||||
import { BaseExtension } from '../../extension'
|
||||
import { fs } from '../../fs'
|
||||
import { Model, ModelEvent } from '../../types'
|
||||
|
||||
/**
|
||||
* Base AIEngine
|
||||
* Applicable to all AI Engines
|
||||
*/
|
||||
export abstract class AIEngine extends BaseExtension {
|
||||
// The inference engine
|
||||
abstract provider: string
|
||||
// The model folder
|
||||
modelFolder: string = 'models'
|
||||
|
||||
models(): Promise<Model[]> {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
/**
|
||||
* On extension load, subscribe to events.
|
||||
*/
|
||||
onLoad() {
|
||||
this.prePopulateModels()
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-populate models to App Data Folder
|
||||
*/
|
||||
prePopulateModels(): Promise<void> {
|
||||
return this.models().then((models) => {
|
||||
const prePoluateOperations = models.map((model) =>
|
||||
getJanDataFolderPath()
|
||||
.then((janDataFolder) =>
|
||||
// Attempt to create the model folder
|
||||
joinPath([janDataFolder, this.modelFolder, model.id]).then((path) =>
|
||||
fs
|
||||
.mkdirSync(path)
|
||||
.catch()
|
||||
.then(() => path)
|
||||
)
|
||||
)
|
||||
.then((path) => joinPath([path, 'model.json']))
|
||||
.then((path) => {
|
||||
// Do not overwite existing model.json
|
||||
return fs.existsSync(path).then((exist: any) => {
|
||||
if (!exist) return fs.writeFileSync(path, JSON.stringify(model, null, 2))
|
||||
})
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error('Error', e)
|
||||
})
|
||||
)
|
||||
Promise.all(prePoluateOperations).then(() =>
|
||||
// Emit event to update models
|
||||
// So the UI can update the models list
|
||||
events.emit(ModelEvent.OnModelsUpdate, {})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
66
core/src/extensions/ai-engines/LocalOAIEngine.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { executeOnMain, getJanDataFolderPath, joinPath, systemInformation } from '../../core'
|
||||
import { events } from '../../events'
|
||||
import { Model, ModelEvent } from '../../types'
|
||||
import { OAIEngine } from './OAIEngine'
|
||||
|
||||
/**
|
||||
* Base OAI Local Inference Provider
|
||||
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||
*/
|
||||
export abstract class LocalOAIEngine extends OAIEngine {
|
||||
// The inference engine
|
||||
abstract nodeModule: string
|
||||
loadModelFunctionName: string = 'loadModel'
|
||||
unloadModelFunctionName: string = 'unloadModel'
|
||||
|
||||
/**
|
||||
* On extension load, subscribe to events.
|
||||
*/
|
||||
onLoad() {
|
||||
super.onLoad()
|
||||
// These events are applicable to local inference providers
|
||||
events.on(ModelEvent.OnModelInit, (model: Model) => this.loadModel(model))
|
||||
events.on(ModelEvent.OnModelStop, (model: Model) => this.unloadModel(model))
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the model.
|
||||
*/
|
||||
async loadModel(model: Model) {
|
||||
if (model.engine.toString() !== this.provider) return
|
||||
|
||||
const modelFolder = await joinPath([await getJanDataFolderPath(), this.modelFolder, model.id])
|
||||
const systemInfo = await systemInformation()
|
||||
const res = await executeOnMain(
|
||||
this.nodeModule,
|
||||
this.loadModelFunctionName,
|
||||
{
|
||||
modelFolder,
|
||||
model,
|
||||
},
|
||||
systemInfo
|
||||
)
|
||||
|
||||
if (res?.error) {
|
||||
events.emit(ModelEvent.OnModelFail, {
|
||||
...model,
|
||||
error: res.error,
|
||||
})
|
||||
return
|
||||
} else {
|
||||
this.loadedModel = model
|
||||
events.emit(ModelEvent.OnModelReady, model)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Stops the model.
|
||||
*/
|
||||
unloadModel(model: Model) {
|
||||
if (model.engine && model.engine?.toString() !== this.provider) return
|
||||
this.loadedModel = undefined
|
||||
|
||||
executeOnMain(this.nodeModule, this.unloadModelFunctionName).then(() => {
|
||||
events.emit(ModelEvent.OnModelStopped, {})
|
||||
})
|
||||
}
|
||||
}
|
||||
128
core/src/extensions/ai-engines/OAIEngine.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import { requestInference } from './helpers/sse'
|
||||
import { ulid } from 'ulidx'
|
||||
import { AIEngine } from './AIEngine'
|
||||
import {
|
||||
ChatCompletionRole,
|
||||
ContentType,
|
||||
InferenceEvent,
|
||||
MessageEvent,
|
||||
MessageRequest,
|
||||
MessageRequestType,
|
||||
MessageStatus,
|
||||
Model,
|
||||
ModelInfo,
|
||||
ThreadContent,
|
||||
ThreadMessage,
|
||||
} from '../../types'
|
||||
import { events } from '../../events'
|
||||
|
||||
/**
|
||||
* Base OAI Inference Provider
|
||||
* Applicable to all OAI compatible inference providers
|
||||
*/
|
||||
export abstract class OAIEngine extends AIEngine {
|
||||
// The inference engine
|
||||
abstract inferenceUrl: string
|
||||
|
||||
// Controller to handle stop requests
|
||||
controller = new AbortController()
|
||||
isCancelled = false
|
||||
|
||||
// The loaded model instance
|
||||
loadedModel: Model | undefined
|
||||
|
||||
/**
|
||||
* On extension load, subscribe to events.
|
||||
*/
|
||||
onLoad() {
|
||||
super.onLoad()
|
||||
events.on(MessageEvent.OnMessageSent, (data: MessageRequest) => this.inference(data))
|
||||
events.on(InferenceEvent.OnInferenceStopped, () => this.stopInference())
|
||||
}
|
||||
|
||||
/**
|
||||
* On extension unload
|
||||
*/
|
||||
onUnload(): void {}
|
||||
|
||||
/*
|
||||
* Inference request
|
||||
*/
|
||||
inference(data: MessageRequest) {
|
||||
if (data.model?.engine?.toString() !== this.provider) return
|
||||
|
||||
const timestamp = Date.now()
|
||||
const message: ThreadMessage = {
|
||||
id: ulid(),
|
||||
thread_id: data.threadId,
|
||||
type: data.type,
|
||||
assistant_id: data.assistantId,
|
||||
role: ChatCompletionRole.Assistant,
|
||||
content: [],
|
||||
status: MessageStatus.Pending,
|
||||
created: timestamp,
|
||||
updated: timestamp,
|
||||
object: 'thread.message',
|
||||
}
|
||||
|
||||
if (data.type !== MessageRequestType.Summary) {
|
||||
events.emit(MessageEvent.OnMessageResponse, message)
|
||||
}
|
||||
|
||||
this.isCancelled = false
|
||||
this.controller = new AbortController()
|
||||
|
||||
const model: ModelInfo = {
|
||||
...(this.loadedModel ? this.loadedModel : {}),
|
||||
...data.model,
|
||||
}
|
||||
|
||||
requestInference(
|
||||
this.inferenceUrl,
|
||||
data.messages ?? [],
|
||||
model,
|
||||
this.controller,
|
||||
this.headers()
|
||||
).subscribe({
|
||||
next: (content: any) => {
|
||||
const messageContent: ThreadContent = {
|
||||
type: ContentType.Text,
|
||||
text: {
|
||||
value: content.trim(),
|
||||
annotations: [],
|
||||
},
|
||||
}
|
||||
message.content = [messageContent]
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
},
|
||||
complete: async () => {
|
||||
message.status = message.content.length ? MessageStatus.Ready : MessageStatus.Error
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
},
|
||||
error: async (err: any) => {
|
||||
if (this.isCancelled || message.content.length) {
|
||||
message.status = MessageStatus.Stopped
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
return
|
||||
}
|
||||
message.status = MessageStatus.Error
|
||||
events.emit(MessageEvent.OnMessageUpdate, message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the inference.
|
||||
*/
|
||||
stopInference() {
|
||||
this.isCancelled = true
|
||||
this.controller?.abort()
|
||||
}
|
||||
|
||||
/**
|
||||
* Headers for the inference request
|
||||
*/
|
||||
headers(): HeadersInit {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
46
core/src/extensions/ai-engines/RemoteOAIEngine.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { events } from '../../events'
|
||||
import { Model, ModelEvent } from '../../types'
|
||||
import { OAIEngine } from './OAIEngine'
|
||||
|
||||
/**
|
||||
* Base OAI Remote Inference Provider
|
||||
* Added the implementation of loading and unloading model (applicable to local inference providers)
|
||||
*/
|
||||
export abstract class RemoteOAIEngine extends OAIEngine {
|
||||
// The inference engine
|
||||
abstract apiKey: string
|
||||
/**
|
||||
* On extension load, subscribe to events.
|
||||
*/
|
||||
onLoad() {
|
||||
super.onLoad()
|
||||
// These events are applicable to local inference providers
|
||||
events.on(ModelEvent.OnModelInit, (model: Model) => this.loadModel(model))
|
||||
events.on(ModelEvent.OnModelStop, (model: Model) => this.unloadModel(model))
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the model.
|
||||
*/
|
||||
async loadModel(model: Model) {
|
||||
if (model.engine.toString() !== this.provider) return
|
||||
events.emit(ModelEvent.OnModelReady, model)
|
||||
}
|
||||
/**
|
||||
* Stops the model.
|
||||
*/
|
||||
unloadModel(model: Model) {
|
||||
if (model.engine && model.engine.toString() !== this.provider) return
|
||||
events.emit(ModelEvent.OnModelStopped, {})
|
||||
}
|
||||
|
||||
/**
|
||||
* Headers for the inference request
|
||||
*/
|
||||
override headers(): HeadersInit {
|
||||
return {
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
'api-key': `${this.apiKey}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { Model } from '@janhq/core'
|
||||
import { Observable } from 'rxjs'
|
||||
import { ModelRuntimeParams } from '../../../types'
|
||||
/**
|
||||
* Sends a request to the inference server to generate a response based on the recent messages.
|
||||
* @param recentMessages - An array of recent messages to use as context for the inference.
|
||||
@ -8,8 +8,12 @@ import { Observable } from 'rxjs'
|
||||
export function requestInference(
|
||||
inferenceUrl: string,
|
||||
recentMessages: any[],
|
||||
model: Model,
|
||||
controller?: AbortController
|
||||
model: {
|
||||
id: string
|
||||
parameters: ModelRuntimeParams
|
||||
},
|
||||
controller?: AbortController,
|
||||
headers?: HeadersInit
|
||||
): Observable<string> {
|
||||
return new Observable((subscriber) => {
|
||||
const requestBody = JSON.stringify({
|
||||
@ -23,9 +27,8 @@ export function requestInference(
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Accept': model.parameters.stream
|
||||
? 'text/event-stream'
|
||||
: 'application/json',
|
||||
'Accept': model.parameters.stream ? 'text/event-stream' : 'application/json',
|
||||
...headers,
|
||||
},
|
||||
body: requestBody,
|
||||
signal: controller?.signal,
|
||||
4
core/src/extensions/ai-engines/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './AIEngine'
|
||||
export * from './OAIEngine'
|
||||
export * from './LocalOAIEngine'
|
||||
export * from './RemoteOAIEngine'
|
||||
@ -28,3 +28,8 @@ export { ModelExtension } from './model'
|
||||
* Hugging Face extension for converting HF models to GGUF.
|
||||
*/
|
||||
export { HuggingFaceExtension } from './huggingface'
|
||||
|
||||
/**
|
||||
* Base AI Engines.
|
||||
*/
|
||||
export * from './ai-engines'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||
import { Model, ModelInterface } from '../index'
|
||||
import { GpuSetting, ImportingModel, Model, ModelInterface, OptionType } from '../index'
|
||||
|
||||
/**
|
||||
* Model extension for managing models.
|
||||
@ -14,6 +14,7 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter
|
||||
|
||||
abstract downloadModel(
|
||||
model: Model,
|
||||
gpuSettings?: GpuSetting,
|
||||
network?: { proxy: string; ignoreSSL?: boolean }
|
||||
): Promise<void>
|
||||
abstract cancelModelDownload(modelId: string): Promise<void>
|
||||
@ -21,4 +22,6 @@ export abstract class ModelExtension extends BaseExtension implements ModelInter
|
||||
abstract saveModel(model: Model): Promise<void>
|
||||
abstract getDownloadedModels(): Promise<Model[]>
|
||||
abstract getConfiguredModels(): Promise<Model[]>
|
||||
abstract importModels(models: ImportingModel[], optionType: OptionType): Promise<void>
|
||||
abstract updateModelInfo(modelInfo: Partial<Model>): Promise<Model>
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { BaseExtension, ExtensionTypeEnum } from '../extension'
|
||||
import { MonitoringInterface } from '../index'
|
||||
import { GpuSetting, MonitoringInterface, OperatingSystemInfo } from '../index'
|
||||
|
||||
/**
|
||||
* Monitoring extension for system monitoring.
|
||||
@ -13,6 +13,8 @@ export abstract class MonitoringExtension extends BaseExtension implements Monit
|
||||
return ExtensionTypeEnum.SystemMonitoring
|
||||
}
|
||||
|
||||
abstract getGpuSetting(): Promise<GpuSetting>
|
||||
abstract getResourcesInfo(): Promise<any>
|
||||
abstract getCurrentLoad(): Promise<any>
|
||||
abstract getOsInfo(): Promise<OperatingSystemInfo>
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import { FileStat } from './types'
|
||||
* Writes data to a file at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves when the file is written successfully.
|
||||
*/
|
||||
const writeFileSync = (...args: any[]) => global.core.api?.writeFileSync(...args)
|
||||
const writeFileSync = (...args: any[]) => globalThis.core.api?.writeFileSync(...args)
|
||||
|
||||
/**
|
||||
* Writes blob data to a file at the specified path.
|
||||
@ -13,47 +13,52 @@ const writeFileSync = (...args: any[]) => global.core.api?.writeFileSync(...args
|
||||
* @returns
|
||||
*/
|
||||
const writeBlob: (path: string, data: string) => Promise<any> = (path, data) =>
|
||||
global.core.api?.writeBlob(path, data)
|
||||
globalThis.core.api?.writeBlob(path, data)
|
||||
|
||||
/**
|
||||
* Reads the contents of a file at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves with the contents of the file.
|
||||
*/
|
||||
const readFileSync = (...args: any[]) => global.core.api?.readFileSync(...args)
|
||||
const readFileSync = (...args: any[]) => globalThis.core.api?.readFileSync(...args)
|
||||
/**
|
||||
* Check whether the file exists
|
||||
* @param {string} path
|
||||
* @returns {boolean} A boolean indicating whether the path is a file.
|
||||
*/
|
||||
const existsSync = (...args: any[]) => global.core.api?.existsSync(...args)
|
||||
const existsSync = (...args: any[]) => globalThis.core.api?.existsSync(...args)
|
||||
/**
|
||||
* List the directory files
|
||||
* @returns {Promise<any>} A Promise that resolves with the contents of the directory.
|
||||
*/
|
||||
const readdirSync = (...args: any[]) => global.core.api?.readdirSync(...args)
|
||||
const readdirSync = (...args: any[]) => globalThis.core.api?.readdirSync(...args)
|
||||
/**
|
||||
* Creates a directory at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves when the directory is created successfully.
|
||||
*/
|
||||
const mkdirSync = (...args: any[]) => global.core.api?.mkdirSync(...args)
|
||||
const mkdirSync = (...args: any[]) => globalThis.core.api?.mkdirSync(...args)
|
||||
|
||||
const mkdir = (...args: any[]) => globalThis.core.api?.mkdir(...args)
|
||||
|
||||
/**
|
||||
* Removes a directory at the specified path.
|
||||
* @returns {Promise<any>} A Promise that resolves when the directory is removed successfully.
|
||||
*/
|
||||
const rmdirSync = (...args: any[]) =>
|
||||
global.core.api?.rmdirSync(...args, { recursive: true, force: true })
|
||||
globalThis.core.api?.rmdirSync(...args, { recursive: true, force: true })
|
||||
|
||||
const rm = (path: string) => globalThis.core.api?.rm(path)
|
||||
|
||||
/**
|
||||
* Deletes a file from the local file system.
|
||||
* @param {string} path - The path of the file to delete.
|
||||
* @returns {Promise<any>} A Promise that resolves when the file is deleted.
|
||||
*/
|
||||
const unlinkSync = (...args: any[]) => global.core.api?.unlinkSync(...args)
|
||||
const unlinkSync = (...args: any[]) => globalThis.core.api?.unlinkSync(...args)
|
||||
|
||||
/**
|
||||
* Appends data to a file at the specified path.
|
||||
*/
|
||||
const appendFileSync = (...args: any[]) => global.core.api?.appendFileSync(...args)
|
||||
const appendFileSync = (...args: any[]) => globalThis.core.api?.appendFileSync(...args)
|
||||
|
||||
/**
|
||||
* Synchronizes a file from a source path to a destination path.
|
||||
@ -62,21 +67,27 @@ const appendFileSync = (...args: any[]) => global.core.api?.appendFileSync(...ar
|
||||
* @returns {Promise<any>} - A promise that resolves when the file has been successfully synchronized.
|
||||
*/
|
||||
const syncFile: (src: string, dest: string) => Promise<any> = (src, dest) =>
|
||||
global.core.api?.syncFile(src, dest)
|
||||
globalThis.core.api?.syncFile(src, dest)
|
||||
|
||||
/**
|
||||
* Copy file sync.
|
||||
*/
|
||||
const copyFileSync = (...args: any[]) => global.core.api?.copyFileSync(...args)
|
||||
const copyFileSync = (...args: any[]) => globalThis.core.api?.copyFileSync(...args)
|
||||
|
||||
const copyFile: (src: string, dest: string) => Promise<void> = (src, dest) =>
|
||||
globalThis.core.api?.copyFile(src, dest)
|
||||
|
||||
/**
|
||||
* Gets the file's stats.
|
||||
*
|
||||
* @param path - The path to the file.
|
||||
* @param outsideJanDataFolder - Whether the file is outside the Jan data folder.
|
||||
* @returns {Promise<FileStat>} - A promise that resolves with the file's stats.
|
||||
*/
|
||||
const fileStat: (path: string) => Promise<FileStat | undefined> = (path) =>
|
||||
global.core.api?.fileStat(path)
|
||||
const fileStat: (path: string, outsideJanDataFolder?: boolean) => Promise<FileStat | undefined> = (
|
||||
path,
|
||||
outsideJanDataFolder
|
||||
) => globalThis.core.api?.fileStat(path, outsideJanDataFolder)
|
||||
|
||||
// TODO: Export `dummy` fs functions automatically
|
||||
// Currently adding these manually
|
||||
@ -86,10 +97,13 @@ export const fs = {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
mkdirSync,
|
||||
mkdir,
|
||||
rmdirSync,
|
||||
rm,
|
||||
unlinkSync,
|
||||
appendFileSync,
|
||||
copyFileSync,
|
||||
copyFile,
|
||||
syncFile,
|
||||
fileStat,
|
||||
writeBlob,
|
||||
|
||||
@ -5,7 +5,7 @@ import { getJanDataFolderPath } from '../../helper'
|
||||
import { DownloadManager } from '../../helper/download'
|
||||
import { createWriteStream, renameSync } from 'fs'
|
||||
import { Processor } from './Processor'
|
||||
import { DownloadState } from '../../../types'
|
||||
import { DownloadRequest, DownloadState, NetworkConfig } from '../../../types'
|
||||
|
||||
export class Downloader implements Processor {
|
||||
observer?: Function
|
||||
@ -20,37 +20,67 @@ export class Downloader implements Processor {
|
||||
return func(this.observer, ...args)
|
||||
}
|
||||
|
||||
downloadFile(observer: any, url: string, localPath: string, network: any) {
|
||||
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') {
|
||||
localPath = normalizeFilePath(localPath)
|
||||
normalizedPath = normalizeFilePath(localPath)
|
||||
}
|
||||
const array = localPath.split(sep)
|
||||
const array = normalizedPath.split(sep)
|
||||
const fileName = array.pop() ?? ''
|
||||
const modelId = array.pop() ?? ''
|
||||
|
||||
const destination = resolve(getJanDataFolderPath(), localPath)
|
||||
const destination = resolve(getJanDataFolderPath(), normalizedPath)
|
||||
const rq = request({ url, strictSSL, proxy })
|
||||
|
||||
// Put request to download manager instance
|
||||
DownloadManager.instance.setRequest(localPath, rq)
|
||||
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,
|
||||
time: {
|
||||
elapsed: 0,
|
||||
remaining: 0,
|
||||
},
|
||||
speed: 0,
|
||||
percent: 0,
|
||||
size: {
|
||||
total: 0,
|
||||
transferred: 0,
|
||||
},
|
||||
children: [],
|
||||
downloadState: 'downloading',
|
||||
extensionId: downloadRequest.extensionId,
|
||||
downloadType: downloadRequest.downloadType,
|
||||
localPath: normalizedPath,
|
||||
}
|
||||
DownloadManager.instance.downloadProgressMap[modelId] = 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,
|
||||
modelId,
|
||||
fileName,
|
||||
fileName: fileName,
|
||||
downloadState: 'downloading',
|
||||
}
|
||||
console.log('progress: ', downloadState)
|
||||
console.debug('progress: ', downloadState)
|
||||
observer?.(DownloadEvent.onFileDownloadUpdate, downloadState)
|
||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||
})
|
||||
@ -58,22 +88,22 @@ export class Downloader implements Processor {
|
||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||
const downloadState: DownloadState = {
|
||||
...currentDownloadState,
|
||||
fileName: fileName,
|
||||
error: error.message,
|
||||
downloadState: 'error',
|
||||
}
|
||||
if (currentDownloadState) {
|
||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||
}
|
||||
|
||||
observer?.(DownloadEvent.onFileDownloadError, downloadState)
|
||||
DownloadManager.instance.downloadProgressMap[modelId] = downloadState
|
||||
})
|
||||
.on('end', () => {
|
||||
const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId]
|
||||
if (currentDownloadState && DownloadManager.instance.networkRequests[localPath]) {
|
||||
if (currentDownloadState && DownloadManager.instance.networkRequests[normalizedPath]) {
|
||||
// Finished downloading, rename temp file to actual file
|
||||
renameSync(downloadingTempFile, destination)
|
||||
const downloadState: DownloadState = {
|
||||
...currentDownloadState,
|
||||
fileName: fileName,
|
||||
downloadState: 'end',
|
||||
}
|
||||
observer?.(DownloadEvent.onFileDownloadSuccess, downloadState)
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { join } from 'path'
|
||||
import fs from 'fs'
|
||||
import { FileManagerRoute } from '../../../api'
|
||||
import { appResourcePath, normalizeFilePath } from '../../helper/path'
|
||||
import { getJanDataFolderPath, getJanDataFolderPath as getPath } from '../../helper'
|
||||
import { Processor } from './Processor'
|
||||
@ -48,10 +47,12 @@ export class FSExt implements Processor {
|
||||
}
|
||||
|
||||
// handle fs is directory here
|
||||
fileStat(path: string) {
|
||||
fileStat(path: string, outsideJanDataFolder?: boolean) {
|
||||
const normalizedPath = normalizeFilePath(path)
|
||||
|
||||
const fullPath = join(getJanDataFolderPath(), normalizedPath)
|
||||
const fullPath = outsideJanDataFolder
|
||||
? normalizedPath
|
||||
: join(getJanDataFolderPath(), normalizedPath)
|
||||
const isExist = fs.existsSync(fullPath)
|
||||
if (!isExist) return undefined
|
||||
|
||||
@ -75,4 +76,40 @@ export class FSExt implements Processor {
|
||||
console.error(`writeFile ${path} result: ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
copyFile(src: string, dest: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.copyFile(src, dest, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
mkdir(path: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdir(path, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
rm(path: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.rm(path, { recursive: true }, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ export const commonRouter = async (app: HttpServer) => {
|
||||
})
|
||||
|
||||
// Threads
|
||||
app.post(`/threads/`, async (req, res) => createThread(req.body))
|
||||
app.post(`/threads`, async (req, res) => createThread(req.body))
|
||||
|
||||
app.get(`/threads/:threadId/messages`, async (req, res) =>
|
||||
getMessages(req.params.threadId).then(normalizeData)
|
||||
|
||||
@ -1,7 +1,16 @@
|
||||
import fs from 'fs'
|
||||
import {
|
||||
existsSync,
|
||||
readdirSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
mkdirSync,
|
||||
appendFileSync,
|
||||
createWriteStream,
|
||||
rmdirSync,
|
||||
} from 'fs'
|
||||
import { JanApiRouteConfiguration, RouteConfiguration } from './configuration'
|
||||
import { join } from 'path'
|
||||
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../index'
|
||||
import { ContentType, MessageStatus, Model, ThreadMessage } from '../../../../types'
|
||||
import { getEngineConfiguration, getJanDataFolderPath } from '../../../helper'
|
||||
import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
||||
|
||||
@ -9,12 +18,12 @@ import { DEFAULT_CHAT_COMPLETION_URL } from './consts'
|
||||
export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||
const directoryPath = join(getJanDataFolderPath(), configuration.dirName)
|
||||
try {
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
if (!existsSync(directoryPath)) {
|
||||
console.debug('model folder not found')
|
||||
return []
|
||||
}
|
||||
|
||||
const files: string[] = fs.readdirSync(directoryPath)
|
||||
const files: string[] = readdirSync(directoryPath)
|
||||
|
||||
const allDirectories: string[] = []
|
||||
for (const file of files) {
|
||||
@ -46,8 +55,8 @@ export const getBuilder = async (configuration: RouteConfiguration) => {
|
||||
}
|
||||
|
||||
const readModelMetadata = (path: string): string | undefined => {
|
||||
if (fs.existsSync(path)) {
|
||||
return fs.readFileSync(path, 'utf-8')
|
||||
if (existsSync(path)) {
|
||||
return readFileSync(path, 'utf-8')
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
@ -81,7 +90,7 @@ export const deleteBuilder = async (configuration: RouteConfiguration, id: strin
|
||||
}
|
||||
|
||||
const objectPath = join(directoryPath, id)
|
||||
fs.rmdirSync(objectPath, { recursive: true })
|
||||
rmdirSync(objectPath, { recursive: true })
|
||||
return {
|
||||
id: id,
|
||||
object: configuration.delete.object,
|
||||
@ -96,20 +105,19 @@ export const getMessages = async (threadId: string): Promise<ThreadMessage[]> =>
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const messageFile = 'messages.jsonl'
|
||||
try {
|
||||
const files: string[] = fs.readdirSync(threadDirPath)
|
||||
const files: string[] = readdirSync(threadDirPath)
|
||||
if (!files.includes(messageFile)) {
|
||||
console.error(`${threadDirPath} not contains message file`)
|
||||
return []
|
||||
}
|
||||
|
||||
const messageFilePath = join(threadDirPath, messageFile)
|
||||
if (!fs.existsSync(messageFilePath)) {
|
||||
if (!existsSync(messageFilePath)) {
|
||||
console.debug('message file not found')
|
||||
return []
|
||||
}
|
||||
|
||||
const lines = fs
|
||||
.readFileSync(messageFilePath, 'utf-8')
|
||||
const lines = readFileSync(messageFilePath, 'utf-8')
|
||||
.toString()
|
||||
.split('\n')
|
||||
.filter((line: any) => line !== '')
|
||||
@ -157,11 +165,11 @@ export const createThread = async (thread: any) => {
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
fs.mkdirSync(threadDirPath)
|
||||
if (!existsSync(threadDirPath)) {
|
||||
mkdirSync(threadDirPath)
|
||||
}
|
||||
|
||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
await writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
return updatedThread
|
||||
} catch (err) {
|
||||
return {
|
||||
@ -191,7 +199,7 @@ export const updateThread = async (threadId: string, thread: any) => {
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', updatedThread.id)
|
||||
const threadJsonPath = join(threadDirPath, threadMetadataFileName)
|
||||
|
||||
await fs.writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
await writeFileSync(threadJsonPath, JSON.stringify(updatedThread, null, 2))
|
||||
return updatedThread
|
||||
} catch (err) {
|
||||
return {
|
||||
@ -208,7 +216,7 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
const threadMessagesFileName = 'messages.jsonl'
|
||||
|
||||
try {
|
||||
const { ulid } = require('ulid')
|
||||
const { ulid } = require('ulidx')
|
||||
const msgId = ulid()
|
||||
const createdAt = Date.now()
|
||||
const threadMessage: ThreadMessage = {
|
||||
@ -233,10 +241,10 @@ export const createMessage = async (threadId: string, message: any) => {
|
||||
const threadDirPath = join(getJanDataFolderPath(), 'threads', threadId)
|
||||
const threadMessagePath = join(threadDirPath, threadMessagesFileName)
|
||||
|
||||
if (!fs.existsSync(threadDirPath)) {
|
||||
fs.mkdirSync(threadDirPath)
|
||||
if (!existsSync(threadDirPath)) {
|
||||
mkdirSync(threadDirPath)
|
||||
}
|
||||
fs.appendFileSync(threadMessagePath, JSON.stringify(threadMessage) + '\n')
|
||||
appendFileSync(threadMessagePath, JSON.stringify(threadMessage) + '\n')
|
||||
return threadMessage
|
||||
} catch (err) {
|
||||
return {
|
||||
@ -259,8 +267,8 @@ export const downloadModel = async (
|
||||
}
|
||||
|
||||
const directoryPath = join(getJanDataFolderPath(), 'models', modelId)
|
||||
if (!fs.existsSync(directoryPath)) {
|
||||
fs.mkdirSync(directoryPath)
|
||||
if (!existsSync(directoryPath)) {
|
||||
mkdirSync(directoryPath)
|
||||
}
|
||||
|
||||
// path to model binary
|
||||
@ -281,7 +289,7 @@ export const downloadModel = async (
|
||||
.on('end', function () {
|
||||
console.debug('end')
|
||||
})
|
||||
.pipe(fs.createWriteStream(modelBinaryPath))
|
||||
.pipe(createWriteStream(modelBinaryPath))
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -41,7 +41,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const modelFolderFullPath = join(janDataFolderPath, 'models', modelId)
|
||||
|
||||
if (!fs.existsSync(modelFolderFullPath)) {
|
||||
throw `Model not found: ${modelId}`
|
||||
throw new Error(`Model not found: ${modelId}`)
|
||||
}
|
||||
|
||||
const files: string[] = fs.readdirSync(modelFolderFullPath)
|
||||
@ -53,7 +53,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const modelMetadata: Model = JSON.parse(fs.readFileSync(modelMetadataPath, 'utf-8'))
|
||||
|
||||
if (!ggufBinFile) {
|
||||
throw 'No GGUF model file found'
|
||||
throw new Error('No GGUF model file found')
|
||||
}
|
||||
const modelBinaryPath = join(modelFolderFullPath, ggufBinFile)
|
||||
|
||||
@ -76,7 +76,7 @@ const runModel = async (modelId: string, settingParams?: ModelSettingParams): Pr
|
||||
const promptTemplate = modelMetadata.settings.prompt_template
|
||||
const prompt = promptTemplateConverter(promptTemplate)
|
||||
if (prompt?.error) {
|
||||
return Promise.reject(prompt.error)
|
||||
throw new Error(prompt.error)
|
||||
}
|
||||
nitroModelSettings.system_prompt = prompt.system_prompt
|
||||
nitroModelSettings.user_prompt = prompt.user_prompt
|
||||
|
||||
@ -93,8 +93,7 @@ export function persistExtensions() {
|
||||
*/
|
||||
export async function installExtensions(extensions: any) {
|
||||
const installed: Extension[] = []
|
||||
for (const ext of extensions) {
|
||||
// Set install options and activation based on input type
|
||||
const installations = extensions.map((ext: any): Promise<void> => {
|
||||
const isObject = typeof ext === 'object'
|
||||
const spec = isObject ? [ext.specifier, ext] : [ext]
|
||||
const activate = isObject ? ext.activate !== false : true
|
||||
@ -102,15 +101,17 @@ export async function installExtensions(extensions: any) {
|
||||
// Install and possibly activate extension
|
||||
const extension = new Extension(...spec)
|
||||
if (!extension.origin) {
|
||||
continue
|
||||
return Promise.resolve()
|
||||
}
|
||||
await extension._install()
|
||||
if (activate) extension.setActive(true)
|
||||
return extension._install().then(() => {
|
||||
if (activate) extension.setActive(true)
|
||||
// Add extension to store if needed
|
||||
addExtension(extension)
|
||||
installed.push(extension)
|
||||
})
|
||||
})
|
||||
|
||||
// Add extension to store if needed
|
||||
addExtension(extension)
|
||||
installed.push(extension)
|
||||
}
|
||||
await Promise.all(installations)
|
||||
|
||||
// Return list of all installed extensions
|
||||
return installed
|
||||
|
||||
@ -4,13 +4,13 @@ import fs from 'fs'
|
||||
import os from 'os'
|
||||
import childProcess from 'child_process'
|
||||
|
||||
// TODO: move this to core
|
||||
const configurationFileName = 'settings.json'
|
||||
|
||||
// TODO: do no specify app name in framework module
|
||||
const defaultJanDataFolder = join(os.homedir(), 'jan')
|
||||
const defaultAppConfig: AppConfiguration = {
|
||||
data_folder: defaultJanDataFolder,
|
||||
quick_ask: false,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,26 +82,34 @@ export const getJanExtensionsPath = (): string => {
|
||||
*/
|
||||
export const physicalCpuCount = async (): Promise<number> => {
|
||||
const platform = os.platform()
|
||||
if (platform === 'linux') {
|
||||
const output = await exec('lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l')
|
||||
return parseInt(output.trim(), 10)
|
||||
} else if (platform === 'darwin') {
|
||||
const output = await exec('sysctl -n hw.physicalcpu_max')
|
||||
return parseInt(output.trim(), 10)
|
||||
} else if (platform === 'win32') {
|
||||
const output = await exec('WMIC CPU Get NumberOfCores')
|
||||
return output
|
||||
.split(os.EOL)
|
||||
.map((line: string) => parseInt(line))
|
||||
.filter((value: number) => !isNaN(value))
|
||||
.reduce((sum: number, number: number) => sum + number, 1)
|
||||
} else {
|
||||
const cores = os.cpus().filter((cpu: any, index: number) => {
|
||||
const hasHyperthreading = cpu.model.includes('Intel')
|
||||
const isOdd = index % 2 === 1
|
||||
return !hasHyperthreading || isOdd
|
||||
})
|
||||
return cores.length
|
||||
try {
|
||||
if (platform === 'linux') {
|
||||
const output = await exec('lscpu -p | egrep -v "^#" | sort -u -t, -k 2,4 | wc -l')
|
||||
return parseInt(output.trim(), 10)
|
||||
} else if (platform === 'darwin') {
|
||||
const output = await exec('sysctl -n hw.physicalcpu_max')
|
||||
return parseInt(output.trim(), 10)
|
||||
} else if (platform === 'win32') {
|
||||
const output = await exec('WMIC CPU Get NumberOfCores')
|
||||
return output
|
||||
.split(os.EOL)
|
||||
.map((line: string) => parseInt(line))
|
||||
.filter((value: number) => !isNaN(value))
|
||||
.reduce((sum: number, number: number) => sum + number, 1)
|
||||
} else {
|
||||
const cores = os.cpus().filter((cpu: any, index: number) => {
|
||||
const hasHyperthreading = cpu.model.includes('Intel')
|
||||
const isOdd = index % 2 === 1
|
||||
return !hasHyperthreading || isOdd
|
||||
})
|
||||
return cores.length
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to get physical CPU count', err)
|
||||
// Divide by 2 to get rid of hyper threading
|
||||
const coreCount = Math.ceil(os.cpus().length / 2)
|
||||
console.debug('Using node API to get physical CPU count:', coreCount)
|
||||
return coreCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +126,7 @@ const exec = async (command: string): Promise<string> => {
|
||||
}
|
||||
|
||||
export const getEngineConfiguration = async (engineId: string) => {
|
||||
if (engineId !== 'openai') {
|
||||
if (engineId !== 'openai' && engineId !== 'groq') {
|
||||
return undefined
|
||||
}
|
||||
const directoryPath = join(getJanDataFolderPath(), 'engines')
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { SystemResourceInfo } from '../../types'
|
||||
import { physicalCpuCount } from './config'
|
||||
import { log, logServer } from './log'
|
||||
import { log } from './log'
|
||||
|
||||
export const getSystemResourceInfo = async (): Promise<SystemResourceInfo> => {
|
||||
const cpu = await physicalCpuCount()
|
||||
|
||||
@ -4,3 +4,5 @@ export * from './extension/manager'
|
||||
export * from './extension/store'
|
||||
export * from './api'
|
||||
export * from './helper'
|
||||
export * from './../types'
|
||||
export * from './../api'
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export type AppConfiguration = {
|
||||
data_folder: string
|
||||
quick_ask: boolean
|
||||
}
|
||||
|
||||
@ -4,16 +4,43 @@ export type FileStat = {
|
||||
}
|
||||
|
||||
export type DownloadState = {
|
||||
modelId: string
|
||||
modelId: string // TODO: change to download id
|
||||
fileName: string
|
||||
time: DownloadTime
|
||||
speed: number
|
||||
percent: number
|
||||
|
||||
percent: number
|
||||
size: DownloadSize
|
||||
children?: DownloadState[]
|
||||
error?: string
|
||||
downloadState: 'downloading' | 'error' | 'end'
|
||||
children?: DownloadState[]
|
||||
|
||||
error?: string
|
||||
extensionId?: string
|
||||
downloadType?: DownloadType
|
||||
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
|
||||
|
||||
downloadType?: DownloadType
|
||||
}
|
||||
|
||||
type DownloadTime = {
|
||||
|
||||
@ -29,6 +29,9 @@ export type ThreadMessage = {
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
type?: string
|
||||
|
||||
/** The error code which explain what error type. Used in conjunction with MessageStatus.Error */
|
||||
error_code?: ErrorCode
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,6 +80,12 @@ export enum MessageStatus {
|
||||
Stopped = 'stopped',
|
||||
}
|
||||
|
||||
export enum ErrorCode {
|
||||
InvalidApiKey = 'invalid_api_key',
|
||||
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
/**
|
||||
* The content type of the message.
|
||||
*/
|
||||
|
||||
7
core/src/types/miscellaneous/appUpdate.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export type AppUpdateInfo = {
|
||||
total: number
|
||||
delta: number
|
||||
transferred: number
|
||||
percent: number
|
||||
bytesPerSecond: number
|
||||
}
|
||||
8
core/src/types/miscellaneous/fileDownloadRequest.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export type FileDownloadRequest = {
|
||||
downloadId: string
|
||||
url: string
|
||||
localPath: string
|
||||
fileName: string
|
||||
displayName: string
|
||||
metadata: Record<string, string | number>
|
||||
}
|
||||
@ -1,2 +1,5 @@
|
||||
export * from './systemResourceInfo'
|
||||
export * from './promptTemplate'
|
||||
export * from './appUpdate'
|
||||
export * from './fileDownloadRequest'
|
||||
export * from './networkConfig'
|
||||
4
core/src/types/miscellaneous/networkConfig.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type NetworkConfig = {
|
||||
proxy?: string
|
||||
ignoreSSL?: boolean
|
||||
}
|
||||
@ -2,3 +2,55 @@ export type SystemResourceInfo = {
|
||||
numCpuPhysicalCore: number
|
||||
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
|
||||
}
|
||||
|
||||
export type GpuSettingInfo = {
|
||||
id: string
|
||||
vram: string
|
||||
name: string
|
||||
arch?: string
|
||||
}
|
||||
|
||||
export type SystemInformation = {
|
||||
gpuSetting: GpuSetting
|
||||
osInfo?: OperatingSystemInfo
|
||||
}
|
||||
|
||||
export const SupportedPlatforms = ['win32', 'linux', 'darwin'] as const
|
||||
export type SupportedPlatformTuple = typeof SupportedPlatforms
|
||||
export type SupportedPlatform = SupportedPlatformTuple[number]
|
||||
|
||||
export type OperatingSystemInfo = {
|
||||
platform: SupportedPlatform | 'unknown'
|
||||
arch: string
|
||||
release: string
|
||||
machine: string
|
||||
version: string
|
||||
totalMem: number
|
||||
freeMem: number
|
||||
}
|
||||
|
||||
export type CpuCoreInfo = {
|
||||
model: string
|
||||
speed: number
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './modelEntity'
|
||||
export * from './modelInterface'
|
||||
export * from './modelEvent'
|
||||
export * from './modelImport'
|
||||
|
||||
@ -7,7 +7,7 @@ export type ModelInfo = {
|
||||
settings: ModelSettingParams
|
||||
parameters: ModelRuntimeParams
|
||||
engine?: InferenceEngine
|
||||
proxyEngine?: InferenceEngine
|
||||
proxy_model?: InferenceEngine
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,7 +18,9 @@ export type ModelInfo = {
|
||||
export enum InferenceEngine {
|
||||
nitro = 'nitro',
|
||||
openai = 'openai',
|
||||
groq = 'groq',
|
||||
triton_trtllm = 'triton_trtllm',
|
||||
nitro_tensorrt_llm = 'nitro-tensorrt-llm',
|
||||
|
||||
tool_retrieval_enabled = 'tool_retrieval_enabled',
|
||||
}
|
||||
@ -93,12 +95,7 @@ export type Model = {
|
||||
*/
|
||||
engine: InferenceEngine
|
||||
|
||||
proxyEngine?: InferenceEngine
|
||||
|
||||
/**
|
||||
* Is multimodal or not.
|
||||
*/
|
||||
visionModel?: boolean
|
||||
proxy_model?: InferenceEngine
|
||||
}
|
||||
|
||||
export type ModelMetadata = {
|
||||
@ -124,6 +121,8 @@ export type ModelSettingParams = {
|
||||
llama_model_path?: string
|
||||
mmproj?: string
|
||||
cont_batching?: boolean
|
||||
vision_model?: boolean
|
||||
text_model?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,3 +140,7 @@ export type ModelRuntimeParams = {
|
||||
presence_penalty?: number
|
||||
engine?: string
|
||||
}
|
||||
|
||||
export type ModelInitFailed = Model & {
|
||||
error: Error
|
||||
}
|
||||
|
||||
23
core/src/types/model/modelImport.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export type OptionType = 'SYMLINK' | 'MOVE_BINARY_FILE'
|
||||
|
||||
export type ModelImportOption = {
|
||||
type: OptionType
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export type ImportingModelStatus = 'PREPARING' | 'IMPORTING' | 'IMPORTED' | 'FAILED'
|
||||
|
||||
export type ImportingModel = {
|
||||
importId: string
|
||||
modelId: string | undefined
|
||||
name: string
|
||||
description: string
|
||||
path: string
|
||||
tags: string[]
|
||||
size: number
|
||||
status: ImportingModelStatus
|
||||
format: string
|
||||
percentage?: number
|
||||
error?: string
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
import { GpuSetting } from '../miscellaneous'
|
||||
import { Model } from './modelEntity'
|
||||
|
||||
/**
|
||||
@ -10,7 +11,11 @@ export interface ModelInterface {
|
||||
* @param network - Optional object to specify proxy/whether to ignore SSL certificates.
|
||||
* @returns A Promise that resolves when the model has been downloaded.
|
||||
*/
|
||||
downloadModel(model: Model, network?: { ignoreSSL?: boolean; proxy?: string }): Promise<void>
|
||||
downloadModel(
|
||||
model: Model,
|
||||
gpuSettings?: GpuSetting,
|
||||
network?: { ignoreSSL?: boolean; proxy?: string }
|
||||
): Promise<void>
|
||||
|
||||
/**
|
||||
* Cancels the download of a specific model.
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from './monitoringInterface'
|
||||
export * from './resourceInfo'
|
||||
|
||||
6
core/src/types/monitoring/resourceInfo.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export type ResourceInfo = {
|
||||
mem: {
|
||||
totalMemory: number
|
||||
usedMemory: number
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@
|
||||
"declarationDir": "dist/types",
|
||||
"outDir": "dist/lib",
|
||||
"importHelpers": true,
|
||||
"types": ["@types/jest"]
|
||||
"types": ["@types/jest"],
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src"],
|
||||
}
|
||||
|
||||
@ -2,4 +2,5 @@ GTM_ID=xxxx
|
||||
UMAMI_PROJECT_API_KEY=xxxx
|
||||
UMAMI_APP_URL=xxxx
|
||||
ALGOLIA_API_KEY=xxxx
|
||||
ALGOLIA_APP_ID=xxxx
|
||||
ALGOLIA_APP_ID=xxxx
|
||||
GITHUB_ACCESS_TOKEN=xxxx
|
||||
@ -1,18 +1,20 @@
|
||||
# Website
|
||||
# Website & Docs
|
||||
|
||||
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
|
||||
This website is built using [Docusaurus 3.0](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
## Information Architecture
|
||||
### Information Architecture
|
||||
|
||||
We try to **keep routes consistent** to maintain SEO.
|
||||
|
||||
- `/guides`: Guides on how to use the Jan application, with GIFs. For end users who are directly using Jan. Always assume users are not technical.
|
||||
- **`/guides/`**: Guides on how to use the Jan application. For end users who are directly using Jan.
|
||||
|
||||
- `/developer`: Developer docs on how to extend Jan. These pages are about what people can build with our software. We must hide the complexity of HOW the app is built, but explain just enough of the high level architecture so devs know enough to build on top of it.
|
||||
- **`/developer/`**: Developer docs on how to extend Jan. These pages are about what people can build with our software.
|
||||
|
||||
- `/api-reference`: Reference documentation, written in Swagger/OpenAPI format.
|
||||
- **`/api-reference/`**: Reference documentation for the Jan API server, written in Swagger/OpenAPI format.
|
||||
|
||||
- `/docs`: Engineering specs and product specs, i.e. HOW the app is built. Mostly for internal reference and for our core contributors who are building the SDK itself.
|
||||
- **`/changelog/`**: A list of changes made to the Jan application with each release.
|
||||
|
||||
- **`/blog/`**: A blog for the Jan application.
|
||||
|
||||
### Sidebar Autogeneration
|
||||
|
||||
@ -20,34 +22,36 @@ The order of each page is either explicitly defined in `sidebar.js` or follows t
|
||||
|
||||
Important slugs are hardcoded at the document level (and shouldn't be rerouted):
|
||||
|
||||
```md
|
||||
```
|
||||
---
|
||||
title: Overview
|
||||
slug: /docs
|
||||
---
|
||||
```
|
||||
|
||||
## Contributing
|
||||
## How to Contribute
|
||||
|
||||
### Installation
|
||||
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.
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
### Pre-requisites and Installation
|
||||
|
||||
### Local Development
|
||||
- [Node.js](https://nodejs.org/en/) (version 20.0.0 or higher)
|
||||
- [yarn](https://yarnpkg.com/) (version 1.22.0 or higher)
|
||||
|
||||
```
|
||||
$ cp .env.example .env
|
||||
$ yarn start
|
||||
#### Installation
|
||||
|
||||
```bash
|
||||
cd jan/docs
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
#### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
@ -56,25 +60,27 @@ This command generates static content into the `build` directory and can be serv
|
||||
|
||||
Using SSH:
|
||||
|
||||
```
|
||||
$ USE_SSH=true yarn deploy
|
||||
```bash
|
||||
USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```bash
|
||||
GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
|
||||
### Preview URL, Pre-release and Publishing Documentation
|
||||
|
||||
When a PR is created, the preview URL will be automatically commented on the PR.
|
||||
- When a pull request is created, the preview URL will be automatically commented on the pull request.
|
||||
|
||||
The documentation will then be published to [https://jan.ai/](https://jan.ai/) when the PR is merged to `main`.
|
||||
- The documentation will then be published to [https://dev.jan.ai/](https://dev.jan.ai/) when the pull request is merged to `dev`.
|
||||
|
||||
- Our open-source maintainers will sync the updated content from `dev` to `docs` branch, which will then be published to [https://jan.ai/](https://jan.ai/).
|
||||
|
||||
### Additional Plugins
|
||||
|
||||
- @docusaurus/theme-live-codeblock
|
||||
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/OpenAPISpec.json`
|
||||
- [Redocusaurus](https://redocusaurus.vercel.app/): manually upload swagger files at `/openapi/jan.yaml` to update the API reference documentation.
|
||||
|
||||
@ -1,8 +1,27 @@
|
||||
---
|
||||
title: "Post Mortem: Bitdefender False Positive Flag"
|
||||
title: 'Post Mortem: Bitdefender False Positive Flag'
|
||||
description: "10th January 2024, Jan's 0.4.4 Release on Windows triggered Bitdefender to incorrectly flag it as infected with Gen:Variant.Tedy.258323, leading to automatic quarantine warnings on users' computers."
|
||||
slug: /postmortems/january-10-2024-bitdefender-false-positive-flag
|
||||
tags: [Postmortem]
|
||||
keywords:
|
||||
[
|
||||
postmortem,
|
||||
bitdefender,
|
||||
false positive,
|
||||
antivirus,
|
||||
jan,
|
||||
nitro,
|
||||
incident,
|
||||
incident response,
|
||||
supply chain security,
|
||||
user communication,
|
||||
documentation,
|
||||
antivirus compatibility,
|
||||
cross-platform testing,
|
||||
proactive incident response,
|
||||
user education,
|
||||
lessons learned,
|
||||
]
|
||||
---
|
||||
|
||||
Following the recent incident related to Jan version 0.4.4 triggering Bitdefender on Windows with Gen:Variant.Tedy.258323 on January 10, 2024, we wanted to provide a comprehensive postmortem and outline the necessary follow-up actions.
|
||||
145
docs/blog/2024-03-19-TensorRT-LLM.md
Normal file
@ -0,0 +1,145 @@
|
||||
---
|
||||
title: Benchmarking TensorRT-LLM vs. llama.cpp
|
||||
description: Jan has added support for the TensorRT-LLM Inference Engine, as an alternative to llama.cpp. We provide a performance benchmark that shows the head-to-head comparison of the two Inference Engine and model formats, with TensorRT-LLM providing better performance but consumes significantly more VRAM and RAM.
|
||||
tags: [Nvidia, TensorRT-LLM, llama.cpp, 3090, 4090, "inference engine"]
|
||||
unlisted: true
|
||||
---
|
||||
|
||||
Jan has added support [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM) as an alternative to the default [llama.cpp](https://github.com/ggerganov/llama.cpp) inference engine. TensorRT-LLM allows Nvidia GPU owners to run blazing fast LLM inference as a hardware-optimized LLM inference engine that compiles models to [run extremely fast on Nvidia GPUs](https://blogs.nvidia.com/blog/tensorrt-llm-windows-stable-diffusion-rtx/).
|
||||
|
||||
You can follow our [TensorRT-LLM Guide](/guides/providers/tensorrt-llm) to try it out today. We've also added a few TensorRT-LLM models to Jan's Model Hub for download:
|
||||
|
||||
- Mistral 7b
|
||||
- TinyLlama-1.1b
|
||||
- TinyJensen-1.1b 😂
|
||||
|
||||
:::tip
|
||||
|
||||
TensorRT-LLM support is available in [v0.4.9](https://github.com/janhq/jan/releases/tag/v0.4.9), but should be considered an experimental feature.
|
||||
|
||||
Please report bugs on [Github](https://github.com/janhq/jan) or on our Discord's [#tensorrt-llm](https://discord.com/channels/1107178041848909847/1201832734704795688) channel.
|
||||
|
||||
:::
|
||||
|
||||
## Performance Benchmarks
|
||||
|
||||
|
||||
We were really curious to see how TensorRT-LLM would perform vs. llama.cpp on consumer-grade GPUs. TensorRT-LLM has previously been shown by Nvidia to reach performance of up to [10,000 tokens/s](https://nvidia.github.io/TensorRT-LLM/blogs/H100vsA100.html) on datacenter-grade GPUs. As most of Jan's users are proud card carrying members of the [GPU Poor](https://www.semianalysis.com/p/google-gemini-eats-the-world-gemini#the-gpu-poor), we wanted to see how the two inference engine performed on the same hardware.
|
||||
|
||||
:::info
|
||||
|
||||
An interesting aside: Jan actually started out in June 2023 building on [FastTransformer](https://github.com/NVIDIA/FasterTransformer), the precursor library to TensorRT-LLM. TensorRT-LLM was released in September 2023, making it a very young library. We're excited to see it's roadmap develop!
|
||||
|
||||
:::
|
||||
|
||||
### Test Setup
|
||||
|
||||
We picked 3 hardware platforms to run the test on, based on Jan's userbase's self-reported common hardware platforms.
|
||||
|
||||
| NVIDIA GPU | VRAM Used (GB) | CUDA Cores | Tensor Cores | Memory Bus Width (bit) | Memory Bandwidth (GB/s) |
|
||||
| ------------------------- | -------------- | ---------- | ------------ | ---------------------- | ----------------------- |
|
||||
| RTX 4090 Desktop (Ada) | 24 | 16,384 | 512 | 384 | ~1000 |
|
||||
| RTX 3090 Desktop (Ampere) | 24 | 10,496 | 328 | 384 | 935.8 |
|
||||
| RTX 4060 Laptop (Ada) | 8 | 3,072 | 96 | 128 | 272 |
|
||||
|
||||
:::warning[Low-spec Machines?]
|
||||
|
||||
We didn't bother including low-spec machines: TensorRT-LLM is meant for performance, and simply doesn't work on lower grade Nvidia GPUs, or computers without GPUs.
|
||||
|
||||
TensorRT-LLM provides blazing fast performance at the cost of [memory usage](https://nvidia.github.io/TensorRT-LLM/memory.html). This means that the performance improvements only show up in higher-range GPUs with larger VRAMs.
|
||||
|
||||
We've found that [llama.cpp](https://github.com/ggerganov/llama.cpp) does an incredible job of democratizing inference to the [GPU Poor](https://www.semianalysis.com/p/google-gemini-eats-the-world-gemini#the-gpu-poor) with CPU-only or lower-range GPUs. Huge shout outs to the [llama.cpp maintainers](https://github.com/ggerganov/llama.cpp/graphs/contributors) and the [ggml.ai](https://ggml.ai/) team.
|
||||
|
||||
:::
|
||||
|
||||
We chose the popular Mistral 7b model to run on both GGUF and TensorRT-LLM, picking comparable quantizations.
|
||||
|
||||
#### llama.cpp Setup
|
||||
- For llama.cpp, we used `Mistral-7b-q4_k_m`
|
||||
- [ ] Fill in `ngl` params, GPU offload etc
|
||||
|
||||
#### TensorRT-LLM Setup
|
||||
- For TensorRT-LLM, we used `Mistral-7b-int4 AWQ`
|
||||
- We ran TensorRT-LLM with `free_gpu_memory_fraction` to test it with the lowest VRAM consumption (performance may be affected)
|
||||
- Note: We picked AWQ for TensorRT-LLM as a handicap as AWQ supposedly sacrifices performance for quality
|
||||
|
||||
#### Experiment Setup
|
||||
We ran the experiment using a standardized inference request in a sandboxed environment on the same machine:
|
||||
- We ran tests 5 times for each inference engine, on a baremetal PC with no other applications open
|
||||
- Each inference request was of `batch_size` 1 and `input_len` 2048, `output_len` 512 as a realistic test case
|
||||
- CPU and Memory usage were obtained from.... Windows Task Manager 😱
|
||||
- GPU usage was obtained from `nvtop`, `htop`, and `nvidia-smi`
|
||||
|
||||
## Results
|
||||
|
||||
Our biggest takeaway: TensorRT-LLM is faster than llama.cpp on 4090s and 3090s with larger VRAMs. However, on smaller GPUs (e.g. Laptop 4060 GPUs),
|
||||
|
||||
| | 4090 Desktop | 3090 Desktop | 4060 Laptop |
|
||||
| ------------ | ------------ | ------------ | ----------- |
|
||||
| TensorRT-LLM | ✅ 159t/s | ✅ 140.27t/s | ❌ 19t/s |
|
||||
| llama.cpp | 101.3t/s | 90t/s | 22t/s |
|
||||
|
||||
### RTX-4090 Desktop
|
||||
|
||||
:::info[Hardware Details]
|
||||
|
||||
- CPU: Intel 13th series
|
||||
- GPU: NVIDIA GPU 4090 (Ampere - sm 86)
|
||||
- RAM: 32GB
|
||||
- OS: Windows 11 Pro on Proxmox
|
||||
|
||||
:::
|
||||
|
||||
Nvidia's RTX-4090 is their top-of-the-line consumer GPU, and retails for [approximately $2,000](https://www.amazon.com/rtx-4090/s?k=rtx+4090).
|
||||
|
||||
#### Mistral-7b int4
|
||||
|
||||
| Metrics | GGUF (using GPU) | TensorRT-LLM | Difference |
|
||||
| -------------------- | -------------------- | ------------ | -------------- |
|
||||
| Throughput (token/s) | 101.3 | 159 | ✅ 57% faster |
|
||||
| VRAM Used (GB) | 5.5 | 6.3 | 🤔 14% more |
|
||||
| RAM Used (GB) | 0.54 | 0.42 | 🤯 20% less |
|
||||
| Disk Size (GB) | 4.07 | 3.66 | 🤯 10% smaller |
|
||||
|
||||
|
||||
### RTX-3090 Desktop
|
||||
|
||||
:::info[Hardware Details]
|
||||
|
||||
- CPU: Intel 13th series
|
||||
- GPU: NVIDIA GPU 3090 (Ampere - sm 86)
|
||||
- RAM: 64GB
|
||||
- OS: Windows
|
||||
|
||||
:::
|
||||
|
||||
#### Mistral-7b int4
|
||||
|
||||
| Metrics | GGUF (using GPU) | TensorRT-LLM | Difference |
|
||||
| -------------------- | -------------------- | ------------ | ------------ |
|
||||
| Throughput (token/s) | 90 | ✅ 140.27 | ✅ 55% faster |
|
||||
| VRAM Used (GB) | 6.0 | 6.8 | 🤔 13% more |
|
||||
| RAM Used (GB) | 0.54 | 0.42 | 🤯 22% less |
|
||||
| Disk Size (GB) | 4.07 | 3.66 | 🤯 10% less |
|
||||
|
||||
### RTX-4060 Laptop
|
||||
|
||||
- [ ] Dan to re-run perf tests and fill in details
|
||||
|
||||
:::info[Hardware Details]
|
||||
|
||||
- Manufacturer: Acer Nitro 16 Phenix
|
||||
- CPU: Ryzen 7000
|
||||
- RAM: 16GB
|
||||
- GPU: NVIDIA Laptop GPU 4060 (Ada)
|
||||
|
||||
:::
|
||||
|
||||
#### Mistral-7b int4
|
||||
|
||||
| Metrics | GGUF (using the GPU) | TensorRT-LLM | Difference |
|
||||
| -------------------- | -------------------- | ------------ | ---------- |
|
||||
| Throughput (token/s) | 22 | ❌ 19 | |
|
||||
| VRAM Used (GB) | 2.1 | 7.7 | |
|
||||
| RAM Used (GB) | 0.3 | 13.5 | |
|
||||
| Disk Size (GB) | 4.07 | 4.07 | |
|
||||
@ -1,56 +1,86 @@
|
||||
---
|
||||
title: About Jan
|
||||
slug: /about
|
||||
description: Jan is a productivity tool to customize AI to your needs and workflows.
|
||||
description: Jan is a desktop application that turns computers into thinking machines.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
about Jan,
|
||||
desktop application,
|
||||
thinking machine,
|
||||
]
|
||||
---
|
||||
|
||||
Jan is a [open-source](https://en.wikipedia.org/wiki/Open_source), [local-first](https://www.inkandswitch.com/local-first/) tool to [create, customize and use AI](https://www.gatesnotes.com/AI-agents) for everyday tasks.
|
||||
Jan turns computers into thinking machines to change how we use them.
|
||||
Jan is created and maintained by Jan Labs, a robotics company.
|
||||
|
||||
You can:
|
||||
With Jan, you can:
|
||||
|
||||
- Run locally using [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/)
|
||||
- Fine-tune AI with specific knowledge
|
||||
- Search the web and other databases
|
||||
- Connect AI to your everyday tools and (with your permission) do work on your behalf
|
||||
- Run [open-source LLMs](https://huggingface.co/models?pipeline_tag=text-generation) locally or connect to cloud AIs like [ChatGPT](https://openai.com/blog/openai-api) or [Google](https://ai.google.dev/).
|
||||
- Fine-tune AI with specific knowledge.
|
||||
- Supercharge your productivity by leveraging AI.
|
||||
- Search the web and databases.
|
||||
- Integrate AI with everyday tools to work on your behalf (with permission).
|
||||
- Customize and add features with Extensions.
|
||||
|
||||
Longer-term, Jan is building a cognitive framework for future robots. We envision a world where we have personal or company robots that we continually improve and customize, growing together with us.
|
||||
:::tip
|
||||
|
||||
Jan aims for long-term human-robot collaboration, envisioning AI as a harmonious extension of human capabilities. Our goal is to build customizable robots that we continually improve and customize, growing together.
|
||||
|
||||
:::
|
||||
|
||||

|
||||
|
||||
## Why do we exist
|
||||
## Jan’s principles
|
||||
|
||||
At Jan, our mission is to advance human-machine collaboration. We achieve this through delivering the best open-source, local-first tools to allow users to run, customize and tinker with AI.
|
||||
- **Ownership**: Jan is committed to developing a product that fully belongs to users. You're the true owner, free from data tracking and storage by us.
|
||||
- **Privacy**: Jan works locally by default, allowing use without an internet connection. Your data stays on your device in a universal format, giving you complete privacy control.
|
||||
- **100% User Supported**: Every user can access, develop, and customize Jan's codebases to suit their needs.
|
||||
- **Rejecting Dark Patterns**: We never use tricks to extract more money or lock you into an ecosystem.
|
||||
|
||||
## What's different about it?
|
||||
## Why do we exist?
|
||||
|
||||
| | Status Quo | Jan |
|
||||
| ---------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Ownership | AI Monopolies owned by Big Tech | AI that you own and control |
|
||||
| Openness? | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
||||
| Your role | Consume | Create, Tinker and Customize |
|
||||
| Approach | Cloud | [Local-first](https://www.inkandswitch.com/local-first/), running 100% on your devices |
|
||||
| Data | Data stored on their servers | Data stored in your local filesystem in open, non-proprietary file formats |
|
||||
| Privacy | 😂 | Runs 100% on your own machine, predictably, privately and offline |
|
||||
| Transparency | "Black Box" | Runs predictability with code available to tinker and customize |
|
||||
| What happens if there's an outage or goes out of business? | Your life's work held hostage in the cloud in proprietary data formats[^1] | Continues to run 100% on your computer, your data is safe in your local folder |
|
||||
| Driving Philosophy | Monetize your users | [Privacy as a human right](https://en.wikipedia.org/wiki/Right_to_privacy) and the [Right to Repair](https://www.repair.org/) |
|
||||
> _"I do not fear computers. I fear the lack of them." - Isaac Asimov_
|
||||
|
||||
## How do I get it?
|
||||
Jan was founded on the belief that AI should coexist with humans, not replace them. Our mission is to democratize AI access, ensuring everyone can easily utilize it with full ownership and control over their data, free from privacy concerns.
|
||||
|
||||
You can install and start using Jan in less than 5 minutes, from [jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
||||
### What are the things Jan committed on?
|
||||
|
||||
You can read the [User Guide](/docs/user-guide) if you need some help to get started.
|
||||
We are committed to creating open, local-first products that extend individual freedom, rejecting dark patterns and ecosystem lock-ins, and embracing an open-source ethos.
|
||||
|
||||
#### What's different about it?
|
||||
|
||||
| | Status Quo | Jan |
|
||||
| --------------------- | -------------------------- | ---------------------------------------------------------------------- |
|
||||
| **Ownership** | Owned by Big Tech | Fully owned by you |
|
||||
| **Openness** | Closed-source | [Open-source (AGPLv3)](https://github.com/janhq/jan/blob/main/LICENSE) |
|
||||
| **Your Role** | Consumer | Creator |
|
||||
| **Approach** | Cloud-based | [Local-first](https://www.inkandswitch.com/local-first/) |
|
||||
| **Data Handling** | Stored on external servers | Stored locally, openly accessible |
|
||||
| **Privacy** | Questionable | Private and offline |
|
||||
| **Transparency** | Opaque "Black Box" | Open-source and customizable |
|
||||
| **Outage Resilience** | Potential data hostage | Continues to work on your device |
|
||||
| **Philosophy** | User monetization | Empowerment with the right to repair |
|
||||
|
||||
## How we work
|
||||
|
||||
Jan is an open-source product with transparent development and future features. Users have the right to modify and customize Jan. We are committed to building an open-source AI ecosystem.
|
||||
|
||||
Jan is building in public using GitHub, where anyone is welcome to join. Key resources include Jan's [Kanban](https://github.com/orgs/janhq/projects/5/views/7) and Jan's [Roadmap](https://github.com/orgs/janhq/projects/5/views/29).
|
||||
|
||||
Jan has a fully-remote team, primarily based in the APAC timezone, and we use Discord and GitHub for collaboration. Our community is central to our operations, and we embrace asynchronous work. We hold meetings only for synchronization and vision sharing, using [Excalidraw](https://excalidraw.com/) or [Miro](https://miro.com/) for visualization and sharing notes on Discord for alignment. We also use [HackMD](https://hackmd.io/) to document our ideas and build a Jan library.
|
||||
|
||||
## How to get it?
|
||||
|
||||
You can install and start using Jan in less than 5 minutes, from [Jan.ai](https://jan.ai) or our [Github repo](https://github.com/janhq/jan).
|
||||
|
||||
## What license is the code under?
|
||||
|
||||
@ -58,8 +88,6 @@ Jan is licensed under the [AGPLv3 License](https://github.com/janhq/jan/blob/mai
|
||||
|
||||
We happily accept pull requests, however, we do ask that you sign a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) so that we have the right to relicense your contributions[^2].
|
||||
|
||||
We also have a [Contributor Program](/docs/team/contributor-program) to provide ownership and upside to contributors who have made significant contributions to the project.
|
||||
|
||||
## What was it built with?
|
||||
|
||||
[Jan](https://github.com/janhq/jan) is pragmatically built using `Typescript` at the application level and `C++` at the Inference level (which we have refactored into [Nitro](https://nitro.jan.ai)[^3]).
|
||||
@ -73,11 +101,9 @@ We follow [clean architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/
|
||||
|
||||
Architecturally, we have made similar choices to the [Next.js Enterprise Javascript Stack](https://vercel.com/templates/next.js/nextjs-enterprise-boilerplate), which is a [battle-tested](https://nextjs.org/showcase/enterprise) framework for building enterprise-grade applications that scale.
|
||||
|
||||
:::tip
|
||||
## Join the team
|
||||
|
||||
**At its core, Jan is a software development kit to build and run copilots on personal devices**. The Desktop Client many folks use is, rather, a specific set of extensions packaged by default. We're excited to see what developers do with the SDK (once its in better shape).
|
||||
|
||||
:::
|
||||
Join us on this journey at Jan Labs, where we embrace open-source collaboration and transparency. Together, let's shape a future where Jan becomes an essential companion in the open-source community. Explore [careers](https://janai.bamboohr.com/careers) with us.
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
BIN
docs/docs/about/assets/solar-punk.webp
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
docs/docs/about/assets/vision-1.webp
Normal file
|
After Width: | Height: | Size: 351 KiB |
69
docs/docs/about/faq.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Frequently Asked Questions (FAQ) - Jan
|
||||
---
|
||||
|
||||
# Frequently Asked Questions (FAQ)
|
||||
|
||||
## What is Jan?
|
||||
|
||||
Jan is software that helps you run large language models (LLMs) on your everyday tasks. For details, read the [About page](https://jan.ai/about/).
|
||||
|
||||
## How do I use Jan?
|
||||
|
||||
Download Jan to your computer, choose a compatible LLM, or connect to a remote AI with the API code to start. You can switch between them as needed.
|
||||
|
||||
## Is Jan compatible with my operating system?
|
||||
|
||||
Jan is available for Mac, Windows, and Linux, ensuring wide compatibility.
|
||||
|
||||
## Do you use my data?
|
||||
|
||||
No. See our data and analytics policy [here](https://jan.ai/privacy/#:~:text=We%20do%20not%20share%20your,with%20a%20better%20user%20experience.).
|
||||
|
||||
## Do you sell my data?
|
||||
|
||||
No. We don't even track your data. Jan is yours.
|
||||
|
||||
## How does Jan ensure my data remains private?
|
||||
|
||||
Jan prioritizes your privacy by running open-source AI models 100% offline on your computer, ensuring all conversations, documents, and files stay private.
|
||||
|
||||
## What does "Jan" stand for?
|
||||
|
||||
Jan stands for “Just Another Neuron”, as we are passionate about building software that complements in your existing neural pathways. But in the spirit of full transparency, it was also just a nice 3 letter domain name we owned 😂.
|
||||
|
||||
## Can I use Jan without an internet connection?
|
||||
|
||||
Yes, Jan can run locally without an internet connection for many features.
|
||||
|
||||
## Are there any costs associated with using Jan?
|
||||
|
||||
Jan is free to use. However, if you want to connect to remote APIs, like GPT-4, you will need to put in your own API key.
|
||||
|
||||
## What types of AI models can I download or import with Jan?
|
||||
|
||||
You can download popular AI models or import any model of your choice through Jan's Hub.
|
||||
|
||||
## How do I customize Jan using the programmable API?
|
||||
|
||||
The API allows you to tailor Jan to your needs, but specific details on usage would require consulting Jan's documentation.
|
||||
|
||||
## How can I contribute to Jan's development or suggest features?
|
||||
|
||||
Contributions can be made through [GitHub](https://github.com/janhq/jan) and [Discord](https://discord.gg/Exe46xPMbK), where you can also suggest features and contribute.
|
||||
|
||||
## How can I get involved with the Jan community?
|
||||
|
||||
Joining [Jan's Discord server](https://discord.gg/qSwXFx6Krr) is a great way to get involved with the community.
|
||||
|
||||
## How do I troubleshoot issues with installing or using Jan?
|
||||
|
||||
For troubleshooting, you should reach out on Discord and check GitHub for assistance and support from the community and the development team.
|
||||
|
||||
## Can I self-host?
|
||||
|
||||
Yes! We love the self-hosted movement. Jan is available as a Helm chart/ Docker composes which can be run across home servers or even production-level environments.
|
||||
|
||||
## Are you hiring?
|
||||
|
||||
We often hire directly from our community. If you are interested in applying, please see our careers page [here](https://janai.bamboohr.com/careers).
|
||||
@ -3,4 +3,4 @@ title: Roadmap
|
||||
---
|
||||
|
||||
- [ ] [Immediate Roadmap on Github](https://github.com/orgs/janhq/projects/5/views/16)
|
||||
- [ ] [Longer-term Roadmap on Discord](https://discord.gg/Ey62mynnYr)
|
||||
- [ ] [Longer-term Roadmap on Discord](https://discord.gg/Ey62mynnYr)
|
||||
|
||||
74
docs/docs/about/vision.md
Normal file
@ -0,0 +1,74 @@
|
||||
---
|
||||
title: Jan's Vision
|
||||
slug: /vision
|
||||
description: Jan is a desktop application that turns computers into thinking machines.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
about Jan,
|
||||
desktop application,
|
||||
thinking machine,
|
||||
jan vision,
|
||||
]
|
||||
---
|
||||
|
||||
## Jan's vision is to shape a future where humans and machines collaborate, continuing our legacy as toolmakers
|
||||
|
||||
Throughout history, humanity has thrived by mastering tools, from [controlling fire](https://en.wikipedia.org/wiki/Control_of_fire_by_early_humans) to [inventing the wheel](https://en.wikipedia.org/wiki/Wheel). These leaps weren't just about survival, they were foundational to our civilization.
|
||||
|
||||
Today, we stand on the brink of a new frontier with artificial intelligence. AI is not merely another tool, it represents a new form of collaboration between humans and machines - promising to enhance our creativity, augment our lives, and deepen our understanding of the world.
|
||||
|
||||

|
||||
|
||||
In the future we envision, AI will be as integral to our lives as fire and the wheel once were, with each individual having their own machines/robots. Mastering AI, like mastering fire, will require understanding its potential, respecting its power, and learning to control it for the betterment of humanity.
|
||||
|
||||
### Inspired by Science Fiction, Grounded in Optimism
|
||||
|
||||
Our vision is influenced by the harmonious coexistence of humans and machines in science fiction. From the helpful companionship of [C3PO](https://tr.wikipedia.org/wiki/C-3PO) and [Jarvis](https://en.wikipedia.org/wiki/J.A.R.V.I.S.) to the strategic alliances in [Halo](https://www.imdb.com/title/tt2934286/), these stories showcase a future where technology amplifies human potential.
|
||||
|
||||
### Jan's Role in Shaping the Future
|
||||
|
||||
Jan is our contribution to this future - a tool designed to augment human capabilities, not replace them. We are committed to developing AI that works for humanity, enhancing our creativity, productivity, and well-being. With Jan, we aim to empower individuals and communities to achieve more, together.
|
||||
|
||||
Our vision is not just a dream, it's a blueprint for a future where technology and humanity harmonize to unlock unprecedented possibilities.
|
||||
|
||||
## How we imagine the world in the future
|
||||
|
||||
We are fundamentally optimistic about the future. Jan aligns with the [Solarpunk movement](https://en.wikipedia.org/wiki/Solarpunk), which envisions a world where technology and nature coexist and flourish together. We reject the notion of climate doomerism and instead, focus on the positive impact we can make with AI.
|
||||
|
||||

|
||||
|
||||
Imagine a world where every individual is empowered by their own robots, where machines are not just tools but partners in our journey. This is the future Jan is striving to create.
|
||||
|
||||
Now, let's take a glimpse into this future through a day in the life of Emre, a reflection of how Jan's vision manifests in everyday life.
|
||||
|
||||
## A Day in the Life of Emre in 2050
|
||||
|
||||
> In 2050, Emre wakes up to the gentle sound of birds chirping, a soothing alarm created by **his own AI robot, Jan**. As he gets ready for the day, **Jan has already prepared** his schedule, factoring in his preferences and the day's weather.
|
||||
>
|
||||
> At breakfast, Emre discusses his upcoming project with **Jan, who offers insights and suggestions**, enhancing Emre's creativity. As he heads to work, his self-driving car, **integrated with Jan**, takes the most scenic and efficient route, allowing Emre to enjoy a moment of tranquility.
|
||||
>
|
||||
> In the office, Emre collaborates with colleagues from around the globe in a virtual workspace. **Jan assists** by translating languages in real-time and organizing ideas, making collaboration seamless and productive.
|
||||
>
|
||||
> During lunch, Emre decides to explore a new hobby. **Jan quickly curates** a list of resources and connects Emre with a virtual mentor, making learning accessible and enjoyable.
|
||||
>
|
||||
> In the afternoon, Emre takes a break to connect with nature. His smart garden, **managed by Jan**, is thriving, blending technology with the natural world in perfect harmony.
|
||||
>
|
||||
> As the day winds down, Emre reflects on his accomplishments. **With Jan's help**, he's been able to focus on what truly matters, achieving a balance between work, personal growth, and well-being.
|
||||
>
|
||||
> In 2050, Jan is more than just a tool, it's an integral part of Emre's life, **augmenting his abilities** and enabling him to live a more fulfilling life.
|
||||
|
||||
What a day, hah!
|
||||
|
||||
---
|
||||
|
||||
Jan's vision commits to developing thinking machines that work alongside humans - learning, adapting, and contributing to a broader, smarter society. This journey isn't just about technology. It's about creating a future where humans and machines collaborate.
|
||||
|
||||
Let's build the future together - join the journey!
|
||||
28
docs/docs/acknowledgements.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Acknowledgements
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
slug: /acknowledgements
|
||||
keywords:
|
||||
[
|
||||
Jan,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language models,
|
||||
acknowledgements,
|
||||
third-party libraries,
|
||||
]
|
||||
---
|
||||
|
||||
# Acknowledgements
|
||||
|
||||
We would like to express our gratitude to the following third-party libraries that have made the development of Jan possible.
|
||||
|
||||
- [llama.cpp](https://github.com/ggerganov/llama.cpp/blob/master/LICENSE)
|
||||
- [LangChain.js](https://github.com/langchain-ai/langchainjs/blob/main/LICENSE)
|
||||
- [TensorRT](https://github.com/NVIDIA/TensorRT/blob/main/LICENSE)
|
||||
- [TensorRT-LLM](https://github.com/NVIDIA/TensorRT-LLM/blob/main/LICENSE)
|
||||
@ -1,31 +0,0 @@
|
||||
---
|
||||
title: Jan's Community
|
||||
slug: /community
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
]
|
||||
---
|
||||
|
||||
## Socials
|
||||
|
||||
- [Discord](https://discord.gg/SH3DGmUs6b)
|
||||
- [X](https://twitter.com/janframework)
|
||||
- [HuggingFace](https://huggingface.co/janhq)
|
||||
- [LinkedIn](https://www.linkedin.com/company/janframework/)
|
||||
|
||||
## Community Run
|
||||
|
||||
- [Reddit](https://www.reddit.com/r/janframework/)
|
||||
|
||||
## Careers
|
||||
|
||||
- [Jobs](https://janai.bamboohr.com/careers)
|
||||
52
docs/docs/community/community.mdx
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Jan's Community
|
||||
slug: /community
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language models,
|
||||
about Jan,
|
||||
desktop application,
|
||||
thinking machine,
|
||||
community,
|
||||
socials,
|
||||
]
|
||||
---
|
||||
|
||||
## Socials
|
||||
|
||||
- [Discord](https://discord.gg/SH3DGmUs6b)
|
||||
- [X](https://twitter.com/janframework)
|
||||
- [HuggingFace](https://huggingface.co/janhq)
|
||||
- [LinkedIn](https://www.linkedin.com/company/janframework/)
|
||||
|
||||
## Community Run
|
||||
|
||||
- [Reddit](https://www.reddit.com/r/janframework/)
|
||||
|
||||
## Careers
|
||||
|
||||
- [Jobs](https://janai.bamboohr.com/careers)
|
||||
|
||||
## Newsletter
|
||||
|
||||
<iframe
|
||||
width="100%"
|
||||
height="600px"
|
||||
src="https://c0c7c086.sibforms.com/serve/MUIFAEWm49nC1OONIibGnlV44yxPMw6Fu1Yc8pK7nP3jp7rZ6rvrb5uOmCD8IIhrRj6-h-_AYrw-sz7JNpcUZ8LAAZoUIOjGmSvNWHwoFhxX5lb-38-fxXj933yIdGzEMBZJv4Nu2BqC2A4uThDGmjM-n_DZBV1v_mKbTcVUWVUE7VutWhRqrDr69IWI4SgbuIMACkcTiWX8ZNLw"
|
||||
frameborder="0"
|
||||
scrolling="auto"
|
||||
allowfullscreen
|
||||
style={{
|
||||
margin: 'auto',
|
||||
maxWidth: '100%',
|
||||
}}
|
||||
></iframe>
|
||||
@ -4,14 +4,16 @@ slug: /developer/architecture
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
architecture,
|
||||
]
|
||||
---
|
||||
|
||||
|
||||
@ -4,14 +4,16 @@ slug: /developer/file-based
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
file based approach,
|
||||
]
|
||||
---
|
||||
|
||||
|
||||
@ -4,14 +4,16 @@ slug: /developer/ui
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
UI kit,
|
||||
]
|
||||
---
|
||||
|
||||
|
||||
@ -4,14 +4,15 @@ slug: /developer/prereq
|
||||
description: Guide to install and setup Jan for development.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
installation,
|
||||
prerequisites,
|
||||
developer setup,
|
||||
@ -24,7 +25,7 @@ keywords:
|
||||
|
||||
Ensure your system meets the following specifications to guarantee a smooth development experience:
|
||||
|
||||
- [Hardware Requirements](../../guides/02-installation/06-hardware.md)
|
||||
- Hardware Requirements
|
||||
|
||||
### System Requirements
|
||||
|
||||
|
||||
@ -4,21 +4,22 @@ slug: /developer
|
||||
description: Jan Docs | Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
The following docs are aimed at developers who want to build extensions on top of the Jan Framework.
|
||||
|
||||
:::tip
|
||||
If you are interested to **contribute to the framework's Core SDK itself**, like adding new drivers, runtimes, and infrastructure level support, please refer to [framework docs](/docs) instead.
|
||||
If you are interested to **contribute to the framework's Core SDK itself**, like adding new drivers, runtimes, and infrastructure level support, please refer to [framework docs](/developer/framework) instead.
|
||||
:::
|
||||
|
||||
## Extensions
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Your First Assistant
|
||||
slug: /developer/build-assistant/your-first-assistant/
|
||||
slug: /developer/assistant/your-first-assistant/
|
||||
description: A quick start on how to build an assistant.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
quick start,
|
||||
build assistant,
|
||||
]
|
||||
@ -20,4 +21,3 @@ keywords:
|
||||
:::caution
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Anatomy of an Assistant
|
||||
slug: /developer/build-assistant/assistant-anatomy/
|
||||
slug: /developer/assistant/assistant-anatomy/
|
||||
description: An overview of assistant.json
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build assistant,
|
||||
assistant anatomy,
|
||||
]
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Package your Assistant
|
||||
slug: /developer/build-assistant/package-your-assistant/
|
||||
slug: /developer/assistant/package-your-assistant/
|
||||
description: Package your assistant for sharing and publishing.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
quick start,
|
||||
build assistant,
|
||||
]
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
---
|
||||
title: Build an Assistant
|
||||
slug: /developer/build-assistant
|
||||
slug: /developer/assistant
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
Jan, Rethink the Computer, local AI, privacy focus, free and open source, private and offline, conversational AI, no-subscription fee, large language models,
|
||||
build assistant,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Your First Engine
|
||||
slug: /developer/build-engine/your-first-engine/
|
||||
slug: /developer/engine/your-first-engine/
|
||||
description: A quick start on how to build your first engine
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
quick start,
|
||||
build engine,
|
||||
]
|
||||
@ -21,4 +22,4 @@ keywords:
|
||||
This is currently under development.
|
||||
:::
|
||||
|
||||
A quickstart on how to integrate tensorrt llm
|
||||
A quickstart on how to integrate tensorrt llm
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Anatomy of an Engine
|
||||
slug: /developer/build-engine/engine-anatomy
|
||||
slug: /developer/engine/engine-anatomy
|
||||
description: An overview of engine.json
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build engine,
|
||||
engine anatomy,
|
||||
]
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Package your Engine
|
||||
slug: /developer/build-engine/package-your-engine/
|
||||
slug: /developer/engine/package-your-engine/
|
||||
description: Package your engine for sharing and publishing.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build engine,
|
||||
engine anatomy,
|
||||
]
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Build an Inference Engine
|
||||
slug: /developer/build-engine/
|
||||
slug: /developer/engine/
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build assistant,
|
||||
]
|
||||
---
|
||||
|
||||
BIN
docs/docs/developer/03-build-engine/asset/plugin.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Your First Extension
|
||||
slug: /developer/build-extension/your-first-extension/
|
||||
slug: /developer/extension/your-first-extension/
|
||||
description: A quick start on how to build your first extension
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
quick start,
|
||||
build extension,
|
||||
]
|
||||
@ -76,13 +77,13 @@ There are a few things to keep in mind when writing your extension code:
|
||||
In `index.ts`, you will see that the extension function will return a `Promise<any>`.
|
||||
|
||||
```typescript
|
||||
import { core } from "@janhq/core";
|
||||
import { core } from '@janhq/core'
|
||||
|
||||
function onStart(): Promise<any> {
|
||||
return core.invokePluginFunc(MODULE_PATH, "run", 0);
|
||||
return core.invokePluginFunc(MODULE_PATH, 'run', 0)
|
||||
}
|
||||
```
|
||||
|
||||
For more information about the Jan Extension Core module, see the [documentation](https://github.com/janhq/jan/blob/main/core/README.md).
|
||||
|
||||
Now, go ahead and start customizing your extension! Happy coding!
|
||||
Now, go ahead and start customizing your extension! Happy coding!
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Anatomy of an Extension
|
||||
slug: /developer/build-extension/extension-anatomy
|
||||
slug: /developer/extension/extension-anatomy
|
||||
description: An overview of extensions.json
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build extension,
|
||||
extension anatomy,
|
||||
]
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Package your Engine
|
||||
slug: /developer/build-extension/package-your-extension/
|
||||
slug: /developer/extension/package-your-extension/
|
||||
description: Package your extension for sharing and publishing.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
build extension,
|
||||
extension anatomy,
|
||||
]
|
||||
|
||||
@ -1,17 +1,10 @@
|
||||
---
|
||||
title: Build an Extension
|
||||
slug: /developer/build-extension/
|
||||
slug: /developer/extension/
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
local AI,
|
||||
private AI,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
Jan, Rethink the Computer, local AI, privacy focus, free and open source, private and offline, conversational AI, no-subscription fee, large language models,
|
||||
build extension,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Engineering Specs
|
||||
slug: /docs/engineering
|
||||
slug: /developer/engineering
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
spec,
|
||||
engineering,
|
||||
]
|
||||
@ -1,16 +1,17 @@
|
||||
---
|
||||
title: "Assistants"
|
||||
title: 'Assistants'
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Chats
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
---
|
||||
title: "Files"
|
||||
title: 'Files'
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,16 +1,17 @@
|
||||
---
|
||||
title: "Fine-tuning"
|
||||
title: 'Fine-tuning'
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Messages
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Models
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Prompts
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Threads
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
---
|
||||
title: Product Specs
|
||||
slug: /docs/product
|
||||
slug: /developer/product
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
spec,
|
||||
product,
|
||||
]
|
||||
@ -3,14 +3,15 @@ title: Chat
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Hub
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Jan (The Default Assistant)
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: Settings
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -3,14 +3,15 @@ title: System Monitor
|
||||
description: Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan AI,
|
||||
Jan,
|
||||
ChatGPT alternative,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
private AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language model,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
@ -1,6 +1,19 @@
|
||||
---
|
||||
title: Overview
|
||||
slug: /docs
|
||||
title: Framework
|
||||
slug: /developer/framework/
|
||||
description: Jan Docs | Jan is a ChatGPT-alternative that runs on your own computer, with a local API server.
|
||||
keywords:
|
||||
[
|
||||
Jan,
|
||||
Rethink the Computer,
|
||||
local AI,
|
||||
privacy focus,
|
||||
free and open source,
|
||||
private and offline,
|
||||
conversational AI,
|
||||
no-subscription fee,
|
||||
large language models,
|
||||
]
|
||||
---
|
||||
|
||||
The following low-level docs are aimed at core contributors.
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 172 KiB |