Merge branch 'dev' into chore/blogpost-finetuning

This commit is contained in:
Henry 2024-03-22 15:10:44 +09:00 committed by GitHub
commit fe503aa844
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
639 changed files with 31931 additions and 7221 deletions

View File

@ -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 {} \;

View File

@ -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

View File

@ -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."
}

View File

@ -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 }}

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

View File

@ -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',

View File

@ -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),
]

View File

@ -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,
}

View File

@ -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 = {

View File

@ -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
}
}

View 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, {})
)
})
}
}

View 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, {})
})
}
}

View 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 {}
}
}

View 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}`,
}
}
}

View File

@ -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,

View File

@ -0,0 +1,4 @@
export * from './AIEngine'
export * from './OAIEngine'
export * from './LocalOAIEngine'
export * from './RemoteOAIEngine'

View File

@ -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'

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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,

View File

@ -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)

View File

@ -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()
}
})
})
}
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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()

View File

@ -4,3 +4,5 @@ export * from './extension/manager'
export * from './extension/store'
export * from './api'
export * from './helper'
export * from './../types'
export * from './../api'

View File

@ -1,3 +1,4 @@
export type AppConfiguration = {
data_folder: string
quick_ask: boolean
}

View File

@ -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 = {

View File

@ -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.
*/

View File

@ -0,0 +1,7 @@
export type AppUpdateInfo = {
total: number
delta: number
transferred: number
percent: number
bytesPerSecond: number
}

View File

@ -0,0 +1,8 @@
export type FileDownloadRequest = {
downloadId: string
url: string
localPath: string
fileName: string
displayName: string
metadata: Record<string, string | number>
}

View File

@ -1,2 +1,5 @@
export * from './systemResourceInfo'
export * from './promptTemplate'
export * from './appUpdate'
export * from './fileDownloadRequest'
export * from './networkConfig'

View File

@ -0,0 +1,4 @@
export type NetworkConfig = {
proxy?: string
ignoreSSL?: boolean
}

View File

@ -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
}

View File

@ -1,3 +1,4 @@
export * from './modelEntity'
export * from './modelInterface'
export * from './modelEvent'
export * from './modelImport'

View File

@ -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
}

View 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
}

View File

@ -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.

View File

@ -1 +1,2 @@
export * from './monitoringInterface'
export * from './resourceInfo'

View File

@ -0,0 +1,6 @@
export type ResourceInfo = {
mem: {
totalMemory: number
usedMemory: number
}
}

View File

@ -13,7 +13,7 @@
"declarationDir": "dist/types",
"outDir": "dist/lib",
"importHelpers": true,
"types": ["@types/jest"]
"types": ["@types/jest"],
},
"include": ["src"]
"include": ["src"],
}

View File

@ -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

View File

@ -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.

View File

@ -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.

View 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 | |

View File

@ -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.
:::
![Human repairing a Droid](/img/star-wars-droids.png)
## Why do we exist
## Jans 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 KiB

69
docs/docs/about/faq.md Normal file
View 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).

View File

@ -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
View 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.
![jan ai shapes the future](./assets/vision-1.webp)
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.
![solarpunk and jan](./assets/solar-punk.webp)
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!

View 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)

View File

@ -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)

View 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>

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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

View File

@ -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

View File

@ -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.
:::

View File

@ -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,
]

View File

@ -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,
]

View File

@ -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,
]
---

View File

@ -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

View File

@ -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,
]

View File

@ -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,
]

View File

@ -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,
]
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -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!

View File

@ -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,
]

View File

@ -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,
]

View File

@ -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,
]
---

View File

@ -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,
]

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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,
]
---

View File

@ -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.

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 402 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Some files were not shown because too many files have changed in this diff Show More