Merge branch 'main' into docs/add-guides
This commit is contained in:
commit
f5b5dfd9b8
@ -70,25 +70,25 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute
|
|||||||
<tr style="text-align:center">
|
<tr style="text-align:center">
|
||||||
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
<td style="text-align:center"><b>Experimental (Nightly Build)</b></td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/0.4.3-118/jan-win-x64-0.4.3-118.exe'>
|
<a href='https://delta.jan.ai/0.4.3-121/jan-win-x64-0.4.3-121.exe'>
|
||||||
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/windows.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.exe</b>
|
<b>jan.exe</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/0.4.3-118/jan-mac-x64-0.4.3-118.dmg'>
|
<a href='https://delta.jan.ai/0.4.3-121/jan-mac-x64-0.4.3-121.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>Intel</b>
|
<b>Intel</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/0.4.3-118/jan-mac-arm64-0.4.3-118.dmg'>
|
<a href='https://delta.jan.ai/0.4.3-121/jan-mac-arm64-0.4.3-121.dmg'>
|
||||||
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
<img src='./docs/static/img/mac.png' style="height:15px; width: 15px" />
|
||||||
<b>M1/M2</b>
|
<b>M1/M2</b>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td style="text-align:center">
|
<td style="text-align:center">
|
||||||
<a href='https://delta.jan.ai/0.4.3-118/jan-linux-amd64-0.4.3-118.deb'>
|
<a href='https://delta.jan.ai/0.4.3-121/jan-linux-amd64-0.4.3-121.deb'>
|
||||||
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
<img src='./docs/static/img/linux.png' style="height:14px; width: 14px" />
|
||||||
<b>jan.deb</b>
|
<b>jan.deb</b>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
60
USAGE.md
Normal file
60
USAGE.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
## Requirements for running Jan App in GPU mode on Windows and Linux
|
||||||
|
- You must have an NVIDIA driver that supports CUDA 11.4 or higher. Refer [here](https://docs.nvidia.com/deploy/cuda-compatibility/index.html#binary-compatibility__table-toolkit-driver).
|
||||||
|
To check if the NVIDIA driver is installed, open PowerShell or Terminal and enter the following command:
|
||||||
|
```bash
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
If you see a result similar to the following, you have successfully installed the NVIDIA driver:
|
||||||
|
```bash
|
||||||
|
+-----------------------------------------------------------------------------+
|
||||||
|
| NVIDIA-SMI 470.57.02 Driver Version: 470.57.02 CUDA Version: 11.4 |
|
||||||
|
|-------------------------------+----------------------+----------------------+
|
||||||
|
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
|
||||||
|
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|
||||||
|
| | | MIG M. |
|
||||||
|
|===============================+======================+======================|
|
||||||
|
| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 On | N/A |
|
||||||
|
| 0% 51C P8 10W / 170W | 364MiB / 7982MiB | 0% Default |
|
||||||
|
| | | N/A |
|
||||||
|
+-------------------------------+----------------------+----------------------+
|
||||||
|
```
|
||||||
|
|
||||||
|
- You must have CUDA 11.4 or higher.
|
||||||
|
To check if CUDA is installed, open PowerShell or Terminal and enter the following command:
|
||||||
|
```bash
|
||||||
|
nvcc --version
|
||||||
|
```
|
||||||
|
If you see a result similar to the following, you have successfully installed CUDA:
|
||||||
|
```bash
|
||||||
|
nvcc: NVIDIA (R) Cuda compiler driver
|
||||||
|
|
||||||
|
Cuda compilation tools, release 11.4, V11.4.100
|
||||||
|
Build cuda_11.4.r11.4/compiler.30033411_0
|
||||||
|
```
|
||||||
|
|
||||||
|
- Specifically for Linux, you will need to have a CUDA compatible driver, refer [here](https://docs.nvidia.com/deploy/cuda-compatibility/index.html#binary-compatibility__table-toolkit-driver), and you must add the `.so` libraries of CUDA and the CUDA compatible driver to the `LD_LIBRARY_PATH` environment variable, refer [here](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#post-installation-actions).
|
||||||
|
|
||||||
|
## How to switch mode CPU/GPU Jan app
|
||||||
|
|
||||||
|
By default, Jan app will run in CPU mode. When starting Jan app, the program will automatically check if your computer meets the requirements to run in GPU mode. If it does, we will automatically enable GPU mode and pick the GPU has highest VGRAM for you (feature allowing users to select one or more GPU devices for use - currently in planning). You can check whether you are using CPU mode or GPU mode in the settings/advance section of Jan app. (see image below). 
|
||||||
|
|
||||||
|
If you have GPU mode but it is not enabled by default, the following possibilities may exist, you can follow the next steps to fix the error:
|
||||||
|
|
||||||
|
1. You have not installed the NVIDIA driver, refer to the NVIDIA driver that supports CUDA 11.4 [here](https://docs.nvidia.com/deploy/cuda-compatibility/index.html#binary-compatibility__table-toolkit-driver).
|
||||||
|
|
||||||
|
2. You have not installed the CUDA toolkit or your CUDA toolkit is not compatible with the NVIDIA driver, refer to CUDA compatibility [here](https://docs.nvidia.com/deploy/cuda-compatibility/index.html#binary-compatibility__table-toolkit-driver).
|
||||||
|
|
||||||
|
3. You have not installed a CUDA compatible driver, refer [here](https://docs.nvidia.com/deploy/cuda-compatibility/index.html#binary-compatibility__table-toolkit-driver), and you must add the `.so` libraries of CUDA and the CUDA compatible driver to the `LD_LIBRARY_PATH` environment variable, refer [here](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#post-installation-actions). For Windows, add the `.dll` libraries of CUDA and the CUDA compatible driver to the `PATH` environment variable. Usually, when installing CUDA on Windows, this environment variable is automatically added, but if you do not see it, you can add it manually by referring [here](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html#environment-setup).
|
||||||
|
|
||||||
|
## To check the current GPU-related settings that Jan app has detected, you can go to the Settings/Advanced section as shown in the image below.
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
When you have an issue with GPU mode, share the `settings.json` with us will help us to solve the problem faster.
|
||||||
|
|
||||||
|
## Tested on
|
||||||
|
|
||||||
|
- Windows 11 Pro 64-bit, NVIDIA GeForce RTX 4070ti GPU, CUDA 12.2, NVIDIA driver 531.18
|
||||||
|
- Ubuntu 22.04 LTS, NVIDIA GeForce RTX 4070ti GPU, CUDA 12.2, NVIDIA driver 545
|
||||||
@ -53,9 +53,10 @@ export enum FileSystemRoute {
|
|||||||
writeFileSync = 'writeFileSync',
|
writeFileSync = 'writeFileSync',
|
||||||
}
|
}
|
||||||
export enum FileManagerRoute {
|
export enum FileManagerRoute {
|
||||||
synceFile = 'syncFile',
|
syncFile = 'syncFile',
|
||||||
getUserSpace = 'getUserSpace',
|
getUserSpace = 'getUserSpace',
|
||||||
getResourcePath = 'getResourcePath',
|
getResourcePath = 'getResourcePath',
|
||||||
|
fileStat = 'fileStat',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiFunction = (...args: any[]) => any
|
export type ApiFunction = (...args: any[]) => any
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { FileStat } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a extension module function in main process
|
* Execute a extension module function in main process
|
||||||
*
|
*
|
||||||
@ -74,6 +76,15 @@ const openExternalUrl: (url: string) => Promise<any> = (url) =>
|
|||||||
*/
|
*/
|
||||||
const getResourcePath: () => Promise<string> = () => global.core.api?.getResourcePath()
|
const getResourcePath: () => Promise<string> = () => global.core.api?.getResourcePath()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file's stats.
|
||||||
|
*
|
||||||
|
* @param path - The path to the file.
|
||||||
|
* @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)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register extension point function type definition
|
* Register extension point function type definition
|
||||||
*/
|
*/
|
||||||
@ -97,4 +108,5 @@ export {
|
|||||||
joinPath,
|
joinPath,
|
||||||
openExternalUrl,
|
openExternalUrl,
|
||||||
baseName,
|
baseName,
|
||||||
|
fileStat,
|
||||||
}
|
}
|
||||||
|
|||||||
12
core/src/node/api/routes/fileManager.ts
Normal file
12
core/src/node/api/routes/fileManager.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { FileManagerRoute } from '../../../api'
|
||||||
|
import { HttpServer } from '../../index'
|
||||||
|
|
||||||
|
export const fsRouter = async (app: HttpServer) => {
|
||||||
|
app.post(`/app/${FileManagerRoute.syncFile}`, async (request: any, reply: any) => {})
|
||||||
|
|
||||||
|
app.post(`/app/${FileManagerRoute.getUserSpace}`, async (request: any, reply: any) => {})
|
||||||
|
|
||||||
|
app.post(`/app/${FileManagerRoute.getResourcePath}`, async (request: any, reply: any) => {})
|
||||||
|
|
||||||
|
app.post(`/app/${FileManagerRoute.fileStat}`, async (request: any, reply: any) => {})
|
||||||
|
}
|
||||||
4
core/src/types/file/index.ts
Normal file
4
core/src/types/file/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type FileStat = {
|
||||||
|
isDirectory: boolean
|
||||||
|
size: number
|
||||||
|
}
|
||||||
@ -4,3 +4,4 @@ export * from './thread'
|
|||||||
export * from './message'
|
export * from './message'
|
||||||
export * from './inference'
|
export * from './inference'
|
||||||
export * from './monitoring'
|
export * from './monitoring'
|
||||||
|
export * from './file'
|
||||||
|
|||||||
BIN
docs/static/img/usage/jan-gpu-enable-setting.png
vendored
Normal file
BIN
docs/static/img/usage/jan-gpu-enable-setting.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
BIN
docs/static/img/usage/jan-open-home-directory.png
vendored
Normal file
BIN
docs/static/img/usage/jan-open-home-directory.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
BIN
docs/static/img/usage/jan-open-settings-1.png
vendored
Normal file
BIN
docs/static/img/usage/jan-open-settings-1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
docs/static/img/usage/jan-open-settings-2.png
vendored
Normal file
BIN
docs/static/img/usage/jan-open-settings-2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/static/img/usage/jan-open-settings-3.png
vendored
Normal file
BIN
docs/static/img/usage/jan-open-settings-3.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
@ -4,14 +4,17 @@ import reflect from '@alumna/reflect'
|
|||||||
|
|
||||||
import { FileManagerRoute } from '@janhq/core'
|
import { FileManagerRoute } from '@janhq/core'
|
||||||
import { userSpacePath, getResourcePath } from './../utils/path'
|
import { userSpacePath, getResourcePath } from './../utils/path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { FileStat } from '@janhq/core/.'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file system extensions operations.
|
* Handles file system extensions operations.
|
||||||
*/
|
*/
|
||||||
export function handleFileMangerIPCs() {
|
export function handleFileMangerIPCs() {
|
||||||
// Handles the 'synceFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
// Handles the 'syncFile' IPC event. This event is triggered to synchronize a file from a source path to a destination path.
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
FileManagerRoute.synceFile,
|
FileManagerRoute.syncFile,
|
||||||
async (_event, src: string, dest: string) => {
|
async (_event, src: string, dest: string) => {
|
||||||
return reflect({
|
return reflect({
|
||||||
src,
|
src,
|
||||||
@ -31,7 +34,33 @@ export function handleFileMangerIPCs() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
|
||||||
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) => {
|
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) =>
|
||||||
return getResourcePath()
|
getResourcePath()
|
||||||
})
|
)
|
||||||
|
|
||||||
|
// handle fs is directory here
|
||||||
|
ipcMain.handle(
|
||||||
|
FileManagerRoute.fileStat,
|
||||||
|
async (_event, path: string): Promise<FileStat | undefined> => {
|
||||||
|
const normalizedPath = path
|
||||||
|
.replace(`file://`, '')
|
||||||
|
.replace(`file:/`, '')
|
||||||
|
.replace(`file:\\\\`, '')
|
||||||
|
.replace(`file:\\`, '')
|
||||||
|
|
||||||
|
const fullPath = join(userSpacePath, normalizedPath)
|
||||||
|
const isExist = fs.existsSync(fullPath)
|
||||||
|
if (!isExist) return undefined
|
||||||
|
|
||||||
|
const isDirectory = fs.lstatSync(fullPath).isDirectory()
|
||||||
|
const size = fs.statSync(fullPath).size
|
||||||
|
|
||||||
|
const fileStat: FileStat = {
|
||||||
|
isDirectory,
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileStat
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0.1.32
|
0.1.34
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
@echo off
|
@echo off
|
||||||
set /p NITRO_VERSION=<./bin/version.txt
|
set /p NITRO_VERSION=<./bin/version.txt
|
||||||
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda.tar.gz -e --strip 1 -o ./bin/win-cuda && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu
|
.\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/win-cuda-12-0 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64-cuda-11-4.tar.gz -e --strip 1 -o ./bin/win-cuda-11-4 && .\node_modules\.bin\download https://github.com/janhq/nitro/releases/download/v%NITRO_VERSION%/nitro-%NITRO_VERSION%-win-amd64.tar.gz -e --strip 1 -o ./bin/win-cpu
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -b . && webpack --config webpack.config.js",
|
"build": "tsc -b . && webpack --config webpack.config.js",
|
||||||
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda.tar.gz -e --strip 1 -o ./bin/linux-cuda && chmod +x ./bin/linux-cuda/nitro",
|
"downloadnitro:linux": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64.tar.gz -e --strip 1 -o ./bin/linux-cpu && chmod +x ./bin/linux-cpu/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-12-0.tar.gz -e --strip 1 -o ./bin/linux-cuda-12-0 && chmod +x ./bin/linux-cuda-12-0/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-linux-amd64-cuda-11-4.tar.gz -e --strip 1 -o ./bin/linux-cuda-11-4 && chmod +x ./bin/linux-cuda-11-4/nitro",
|
||||||
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
"downloadnitro:darwin": "NITRO_VERSION=$(cat ./bin/version.txt) && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-arm64.tar.gz -e --strip 1 -o ./bin/mac-arm64 && chmod +x ./bin/mac-arm64/nitro && download https://github.com/janhq/nitro/releases/download/v${NITRO_VERSION}/nitro-${NITRO_VERSION}-mac-amd64.tar.gz -e --strip 1 -o ./bin/mac-x64 && chmod +x ./bin/mac-x64/nitro",
|
||||||
"downloadnitro:win32": "download.bat",
|
"downloadnitro:win32": "download.bat",
|
||||||
"downloadnitro": "run-script-os",
|
"downloadnitro": "run-script-os",
|
||||||
|
|||||||
@ -30,7 +30,10 @@ export function requestInference(
|
|||||||
signal: controller?.signal,
|
signal: controller?.signal,
|
||||||
})
|
})
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
if (model.parameters.stream) {
|
if (model.parameters.stream === false) {
|
||||||
|
const data = await response.json();
|
||||||
|
subscriber.next(data.choices[0]?.message?.content ?? "");
|
||||||
|
} else {
|
||||||
const stream = response.body;
|
const stream = response.body;
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
const reader = stream?.getReader();
|
const reader = stream?.getReader();
|
||||||
@ -54,9 +57,6 @@ export function requestInference(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const data = await response.json();
|
|
||||||
subscriber.next(data.choices[0]?.message?.content ?? "");
|
|
||||||
}
|
}
|
||||||
subscriber.complete();
|
subscriber.complete();
|
||||||
})
|
})
|
||||||
|
|||||||
@ -85,28 +85,40 @@ function checkFileExistenceInPaths(file: string, paths: string[]): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateCudaExistence() {
|
function updateCudaExistence() {
|
||||||
let files: string[];
|
let filesCuda12: string[];
|
||||||
|
let filesCuda11: string[];
|
||||||
let paths: string[];
|
let paths: string[];
|
||||||
|
let cudaVersion: string = "";
|
||||||
|
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
files = ["cublas64_12.dll", "cudart64_12.dll", "cublasLt64_12.dll"];
|
filesCuda12 = ["cublas64_12.dll", "cudart64_12.dll", "cublasLt64_12.dll"];
|
||||||
|
filesCuda11 = ["cublas64_11.dll", "cudart64_11.dll", "cublasLt64_11.dll"];
|
||||||
paths = process.env.PATH ? process.env.PATH.split(path.delimiter) : [];
|
paths = process.env.PATH ? process.env.PATH.split(path.delimiter) : [];
|
||||||
const nitro_cuda_path = path.join(__dirname, "bin", "win-cuda");
|
|
||||||
paths.push(nitro_cuda_path);
|
|
||||||
} else {
|
} else {
|
||||||
files = ["libcudart.so.12", "libcublas.so.12", "libcublasLt.so.12"];
|
filesCuda12 = ["libcudart.so.12", "libcublas.so.12", "libcublasLt.so.12"];
|
||||||
|
filesCuda11 = ["libcudart.so.11.0", "libcublas.so.11", "libcublasLt.so.11"];
|
||||||
paths = process.env.LD_LIBRARY_PATH
|
paths = process.env.LD_LIBRARY_PATH
|
||||||
? process.env.LD_LIBRARY_PATH.split(path.delimiter)
|
? process.env.LD_LIBRARY_PATH.split(path.delimiter)
|
||||||
: [];
|
: [];
|
||||||
const nitro_cuda_path = path.join(__dirname, "bin", "linux-cuda");
|
|
||||||
paths.push(nitro_cuda_path);
|
|
||||||
paths.push("/usr/lib/x86_64-linux-gnu/");
|
paths.push("/usr/lib/x86_64-linux-gnu/");
|
||||||
}
|
}
|
||||||
|
|
||||||
let cudaExists = files.every(
|
let cudaExists = filesCuda12.every(
|
||||||
(file) => existsSync(file) || checkFileExistenceInPaths(file, paths)
|
(file) => existsSync(file) || checkFileExistenceInPaths(file, paths)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!cudaExists) {
|
||||||
|
cudaExists = filesCuda11.every(
|
||||||
|
(file) => existsSync(file) || checkFileExistenceInPaths(file, paths)
|
||||||
|
);
|
||||||
|
if (cudaExists) {
|
||||||
|
cudaVersion = "11";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cudaVersion = "12";
|
||||||
|
}
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
@ -115,6 +127,7 @@ function updateCudaExistence() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data["cuda"].exist = cudaExists;
|
data["cuda"].exist = cudaExists;
|
||||||
|
data["cuda"].version = cudaVersion;
|
||||||
if (cudaExists) {
|
if (cudaExists) {
|
||||||
data.run_mode = "gpu";
|
data.run_mode = "gpu";
|
||||||
}
|
}
|
||||||
@ -376,12 +389,17 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
|||||||
let cudaVisibleDevices = "";
|
let cudaVisibleDevices = "";
|
||||||
let binaryName;
|
let binaryName;
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
if (nvida_info["run_mode"] === "cpu") {
|
if (nvidiaInfo["run_mode"] === "cpu") {
|
||||||
binaryFolder = path.join(binaryFolder, "win-cpu");
|
binaryFolder = path.join(binaryFolder, "win-cpu");
|
||||||
} else {
|
} else {
|
||||||
binaryFolder = path.join(binaryFolder, "win-cuda");
|
if (nvidiaInfo["cuda"].version === "12") {
|
||||||
cudaVisibleDevices = nvida_info["gpu_highest_vram"];
|
binaryFolder = path.join(binaryFolder, "win-cuda-12-0");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
binaryFolder = path.join(binaryFolder, "win-cuda-11-4");
|
||||||
|
}
|
||||||
|
cudaVisibleDevices = nvidiaInfo["gpu_highest_vram"];
|
||||||
}
|
}
|
||||||
binaryName = "nitro.exe";
|
binaryName = "nitro.exe";
|
||||||
} else if (process.platform === "darwin") {
|
} else if (process.platform === "darwin") {
|
||||||
@ -392,12 +410,17 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
|
|||||||
}
|
}
|
||||||
binaryName = "nitro";
|
binaryName = "nitro";
|
||||||
} else {
|
} else {
|
||||||
let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
|
||||||
if (nvida_info["run_mode"] === "cpu") {
|
if (nvidiaInfo["run_mode"] === "cpu") {
|
||||||
binaryFolder = path.join(binaryFolder, "linux-cpu");
|
binaryFolder = path.join(binaryFolder, "linux-cpu");
|
||||||
} else {
|
} else {
|
||||||
binaryFolder = path.join(binaryFolder, "linux-cuda");
|
if (nvidiaInfo["cuda"].version === "12") {
|
||||||
cudaVisibleDevices = nvida_info["gpu_highest_vram"];
|
binaryFolder = path.join(binaryFolder, "linux-cuda-12-0");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
binaryFolder = path.join(binaryFolder, "linux-cuda-11-4");
|
||||||
|
}
|
||||||
|
cudaVisibleDevices = nvidiaInfo["gpu_highest_vram"];
|
||||||
}
|
}
|
||||||
binaryName = "nitro";
|
binaryName = "nitro";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,10 @@ export function requestInference(
|
|||||||
subscriber.complete();
|
subscriber.complete();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (model.parameters.stream) {
|
if (model.parameters.stream === false) {
|
||||||
|
const data = await response.json();
|
||||||
|
subscriber.next(data.choices[0]?.message?.content ?? "");
|
||||||
|
} else {
|
||||||
const stream = response.body;
|
const stream = response.body;
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
const reader = stream?.getReader();
|
const reader = stream?.getReader();
|
||||||
@ -70,9 +73,6 @@ export function requestInference(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const data = await response.json();
|
|
||||||
subscriber.next(data.choices[0]?.message?.content ?? "");
|
|
||||||
}
|
}
|
||||||
subscriber.complete();
|
subscriber.complete();
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,11 +5,12 @@ import {
|
|||||||
abortDownload,
|
abortDownload,
|
||||||
getResourcePath,
|
getResourcePath,
|
||||||
getUserSpace,
|
getUserSpace,
|
||||||
|
fileStat,
|
||||||
InferenceEngine,
|
InferenceEngine,
|
||||||
joinPath,
|
joinPath,
|
||||||
|
ModelExtension,
|
||||||
|
Model,
|
||||||
} from '@janhq/core'
|
} from '@janhq/core'
|
||||||
import { ModelExtension, Model } from '@janhq/core'
|
|
||||||
import { baseName } from '@janhq/core/.'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A extension for models
|
* A extension for models
|
||||||
@ -21,6 +22,9 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
private static readonly _incompletedModelFileName = '.download'
|
private static readonly _incompletedModelFileName = '.download'
|
||||||
private static readonly _offlineInferenceEngine = InferenceEngine.nitro
|
private static readonly _offlineInferenceEngine = InferenceEngine.nitro
|
||||||
|
|
||||||
|
private static readonly _configDirName = 'config'
|
||||||
|
private static readonly _defaultModelFileName = 'default-model.json'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements type from JanExtension.
|
* Implements type from JanExtension.
|
||||||
* @override
|
* @override
|
||||||
@ -199,7 +203,7 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
): Promise<Model[]> {
|
): Promise<Model[]> {
|
||||||
try {
|
try {
|
||||||
if (!(await fs.existsSync(JanModelExtension._homeDir))) {
|
if (!(await fs.existsSync(JanModelExtension._homeDir))) {
|
||||||
console.debug('model folder not found')
|
console.error('Model folder not found')
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,13 +224,22 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
dirName,
|
dirName,
|
||||||
JanModelExtension._modelMetadataFileName,
|
JanModelExtension._modelMetadataFileName,
|
||||||
])
|
])
|
||||||
let model = await this.readModelMetadata(jsonPath)
|
|
||||||
model = typeof model === 'object' ? model : JSON.parse(model)
|
|
||||||
|
|
||||||
if (selector && !(await selector?.(dirName, model))) {
|
if (await fs.existsSync(jsonPath)) {
|
||||||
return
|
// if we have the model.json file, read it
|
||||||
|
let model = await this.readModelMetadata(jsonPath)
|
||||||
|
model = typeof model === 'object' ? model : JSON.parse(model)
|
||||||
|
|
||||||
|
if (selector && !(await selector?.(dirName, model))) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return model
|
||||||
|
} else {
|
||||||
|
// otherwise, we generate our own model file
|
||||||
|
// TODO: we might have more than one binary file here. This will be addressed with new version of Model file
|
||||||
|
// which is the PR from Hiro on branch Jan can see
|
||||||
|
return this.generateModelMetadata(dirName)
|
||||||
}
|
}
|
||||||
return model
|
|
||||||
})
|
})
|
||||||
const results = await Promise.allSettled(readJsonPromises)
|
const results = await Promise.allSettled(readJsonPromises)
|
||||||
const modelData = results.map((result) => {
|
const modelData = results.map((result) => {
|
||||||
@ -254,6 +267,84 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
return fs.readFileSync(path, 'utf-8')
|
return fs.readFileSync(path, 'utf-8')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the case where we have the model directory but we don't have the corresponding
|
||||||
|
* model.json file associated with it.
|
||||||
|
*
|
||||||
|
* This function will create a model.json file for the model.
|
||||||
|
*
|
||||||
|
* @param dirName the director which reside in ~/jan/models but does not have model.json file.
|
||||||
|
*/
|
||||||
|
private async generateModelMetadata(dirName: string): Promise<Model> {
|
||||||
|
const files: string[] = await fs.readdirSync(
|
||||||
|
await joinPath([JanModelExtension._homeDir, dirName])
|
||||||
|
)
|
||||||
|
|
||||||
|
// sort files by name
|
||||||
|
files.sort()
|
||||||
|
|
||||||
|
// find the first file which is not a directory
|
||||||
|
let binaryFileName: string | undefined = undefined
|
||||||
|
let binaryFileSize: number | undefined = undefined
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith(JanModelExtension._incompletedModelFileName)) continue
|
||||||
|
if (file.endsWith('.json')) continue
|
||||||
|
|
||||||
|
const path = await joinPath([JanModelExtension._homeDir, dirName, file])
|
||||||
|
const fileStats = await fileStat(path)
|
||||||
|
if (fileStats.isDirectory) continue
|
||||||
|
binaryFileSize = fileStats.size
|
||||||
|
binaryFileName = file
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!binaryFileName) {
|
||||||
|
console.warn(`Unable to find binary file for model ${dirName}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultModel = await this.getDefaultModel()
|
||||||
|
if (!defaultModel) {
|
||||||
|
console.error('Unable to find default model')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const model: Model = {
|
||||||
|
...defaultModel,
|
||||||
|
id: dirName,
|
||||||
|
name: dirName,
|
||||||
|
created: Date.now(),
|
||||||
|
description: `${dirName} - user self import model`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelFilePath = await joinPath([
|
||||||
|
JanModelExtension._homeDir,
|
||||||
|
dirName,
|
||||||
|
JanModelExtension._modelMetadataFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
await fs.writeFileSync(modelFilePath, JSON.stringify(model, null, 2))
|
||||||
|
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDefaultModel(): Promise<Model | undefined> {
|
||||||
|
const defaultModelPath = await joinPath([
|
||||||
|
JanModelExtension._homeDir,
|
||||||
|
JanModelExtension._configDirName,
|
||||||
|
JanModelExtension._defaultModelFileName,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!(await fs.existsSync(defaultModelPath))) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = await this.readModelMetadata(defaultModelPath)
|
||||||
|
|
||||||
|
return typeof model === 'object' ? model : JSON.parse(model)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all available models.
|
* Gets all available models.
|
||||||
* @returns A Promise that resolves with an array of all models.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
|
|||||||
35
models/config/default-model.json
Normal file
35
models/config/default-model.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"object": "model",
|
||||||
|
"version": 1,
|
||||||
|
"format": "gguf",
|
||||||
|
"source_url": "N/A",
|
||||||
|
"id": "N/A",
|
||||||
|
"name": "N/A",
|
||||||
|
"created": 0,
|
||||||
|
"description": "User self import model",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 4096,
|
||||||
|
"ngl": 0,
|
||||||
|
"embedding": false,
|
||||||
|
"n_parallel": 0,
|
||||||
|
"cpu_threads": 0,
|
||||||
|
"prompt_template": ""
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0,
|
||||||
|
"token_limit": 0,
|
||||||
|
"top_k": 0,
|
||||||
|
"top_p": 0,
|
||||||
|
"stream": false,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"stop": [],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "User",
|
||||||
|
"tags": [],
|
||||||
|
"size": 0
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@
|
|||||||
"build": "tsc"
|
"build": "tsc"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@alumna/reflect": "^1.1.3",
|
||||||
"@fastify/cors": "^8.4.2",
|
"@fastify/cors": "^8.4.2",
|
||||||
"@fastify/static": "^6.12.0",
|
"@fastify/static": "^6.12.0",
|
||||||
"@fastify/swagger": "^8.13.0",
|
"@fastify/swagger": "^8.13.0",
|
||||||
|
|||||||
@ -36,28 +36,23 @@ const GPUDriverPrompt: React.FC = () => {
|
|||||||
<Modal open={showNotification} onOpenChange={openChanged}>
|
<Modal open={showNotification} onOpenChange={openChanged}>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<ModalTitle>Missing Nvidia Driver and Cuda Toolkit</ModalTitle>
|
<ModalTitle>
|
||||||
|
Checking for machine that does not meet the requirements.
|
||||||
|
</ModalTitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>
|
<p>
|
||||||
It seems like you are missing Nvidia Driver or Cuda Toolkit or both.
|
It appears that you are missing some dependencies required to run in
|
||||||
Please follow the instructions on the{' '}
|
GPU mode. Please follow the instructions below for more details{' '}
|
||||||
<span
|
<span
|
||||||
className="cursor-pointer text-blue-600"
|
className="cursor-pointer text-blue-600"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openExternalUrl('https://developer.nvidia.com/cuda-toolkit')
|
openExternalUrl(
|
||||||
|
'https://github.com/janhq/jan/blob/main/USAGE.md'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
NVidia Cuda Toolkit Installation Page
|
Jan running mode documentation
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
and the{' '}
|
|
||||||
<span
|
|
||||||
className="cursor-pointer text-blue-600"
|
|
||||||
onClick={() =>
|
|
||||||
openExternalUrl('https://www.nvidia.com/Download/index.aspx')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Nvidia Driver Installation Page
|
|
||||||
</span>
|
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|||||||
@ -22,8 +22,12 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
const modelsRef = useRef(models)
|
const modelsRef = useRef(models)
|
||||||
|
|
||||||
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
|
||||||
const { setDownloadState, setDownloadStateSuccess, setDownloadStateFailed } =
|
const {
|
||||||
useDownloadState()
|
setDownloadState,
|
||||||
|
setDownloadStateSuccess,
|
||||||
|
setDownloadStateFailed,
|
||||||
|
setDownloadStateCancelled,
|
||||||
|
} = useDownloadState()
|
||||||
const downloadedModelRef = useRef(downloadedModels)
|
const downloadedModelRef = useRef(downloadedModels)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -52,13 +56,18 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
|
|
||||||
window.electronAPI.onFileDownloadError(
|
window.electronAPI.onFileDownloadError(
|
||||||
async (_event: string, state: any) => {
|
async (_event: string, state: any) => {
|
||||||
if (state.err?.message !== 'aborted')
|
|
||||||
console.error('Download error', state)
|
|
||||||
const modelName = await baseName(state.fileName)
|
const modelName = await baseName(state.fileName)
|
||||||
const model = modelsRef.current.find(
|
const model = modelsRef.current.find(
|
||||||
(model) => modelBinFileName(model) === modelName
|
(model) => modelBinFileName(model) === modelName
|
||||||
)
|
)
|
||||||
if (model) setDownloadStateFailed(model.id)
|
if (model) {
|
||||||
|
if (state.err?.message !== 'aborted') {
|
||||||
|
console.error('Download error', state)
|
||||||
|
setDownloadStateFailed(model.id, state.err.message)
|
||||||
|
} else {
|
||||||
|
setDownloadStateCancelled(model.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -29,26 +29,49 @@ const setDownloadStateSuccessAtom = atom(null, (get, set, modelId: string) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const setDownloadStateFailedAtom = atom(null, (get, set, modelId: string) => {
|
const setDownloadStateFailedAtom = atom(
|
||||||
const currentState = { ...get(modelDownloadStateAtom) }
|
null,
|
||||||
const state = currentState[modelId]
|
(get, set, modelId: string, error: string) => {
|
||||||
if (!state) {
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
console.debug(`Cannot find download state for ${modelId}`)
|
const state = currentState[modelId]
|
||||||
toaster({
|
if (!state) {
|
||||||
title: 'Cancel Download',
|
console.debug(`Cannot find download state for ${modelId}`)
|
||||||
description: `Model ${modelId} cancel download`,
|
toaster({
|
||||||
})
|
title: 'Download Failed',
|
||||||
return
|
description: `Model ${modelId} download failed: ${error}`,
|
||||||
|
type: 'error',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete currentState[modelId]
|
||||||
|
set(modelDownloadStateAtom, currentState)
|
||||||
}
|
}
|
||||||
delete currentState[modelId]
|
)
|
||||||
set(modelDownloadStateAtom, currentState)
|
const setDownloadStateCancelledAtom = atom(
|
||||||
})
|
null,
|
||||||
|
(get, set, modelId: string) => {
|
||||||
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
|
const state = currentState[modelId]
|
||||||
|
if (!state) {
|
||||||
|
console.debug(`Cannot find download state for ${modelId}`)
|
||||||
|
toaster({
|
||||||
|
title: 'Cancel Download',
|
||||||
|
description: `Model ${modelId} cancel download`,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete currentState[modelId]
|
||||||
|
set(modelDownloadStateAtom, currentState)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
export function useDownloadState() {
|
export function useDownloadState() {
|
||||||
const modelDownloadState = useAtomValue(modelDownloadStateAtom)
|
const modelDownloadState = useAtomValue(modelDownloadStateAtom)
|
||||||
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
const setDownloadState = useSetAtom(setDownloadStateAtom)
|
||||||
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom)
|
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom)
|
||||||
const setDownloadStateFailed = useSetAtom(setDownloadStateFailedAtom)
|
const setDownloadStateFailed = useSetAtom(setDownloadStateFailedAtom)
|
||||||
|
const setDownloadStateCancelled = useSetAtom(setDownloadStateCancelledAtom)
|
||||||
|
|
||||||
const downloadStates: DownloadState[] = []
|
const downloadStates: DownloadState[] = []
|
||||||
for (const [, value] of Object.entries(modelDownloadState)) {
|
for (const [, value] of Object.entries(modelDownloadState)) {
|
||||||
@ -61,6 +84,7 @@ export function useDownloadState() {
|
|||||||
setDownloadState,
|
setDownloadState,
|
||||||
setDownloadStateSuccess,
|
setDownloadStateSuccess,
|
||||||
setDownloadStateFailed,
|
setDownloadStateFailed,
|
||||||
|
setDownloadStateCancelled,
|
||||||
downloadStates,
|
downloadStates,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ChatCompletionRole, MessageStatus, ThreadMessage } from '@janhq/core'
|
|||||||
import hljs from 'highlight.js'
|
import hljs from 'highlight.js'
|
||||||
|
|
||||||
import { useAtomValue } from 'jotai'
|
import { useAtomValue } from 'jotai'
|
||||||
import { Marked } from 'marked'
|
import { Marked, Renderer } from 'marked'
|
||||||
|
|
||||||
import { markedHighlight } from 'marked-highlight'
|
import { markedHighlight } from 'marked-highlight'
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
|
|||||||
}
|
}
|
||||||
const clipboard = useClipboard({ timeout: 1000 })
|
const clipboard = useClipboard({ timeout: 1000 })
|
||||||
|
|
||||||
const marked = new Marked(
|
const marked: Marked = new Marked(
|
||||||
markedHighlight({
|
markedHighlight({
|
||||||
langPrefix: 'hljs',
|
langPrefix: 'hljs',
|
||||||
highlight(code, lang) {
|
highlight(code, lang) {
|
||||||
@ -46,6 +46,11 @@ const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
renderer: {
|
renderer: {
|
||||||
|
link: (href, title, text) => {
|
||||||
|
return Renderer.prototype.link
|
||||||
|
?.apply(this, [href, title, text])
|
||||||
|
.replace('<a', "<a target='_blank'")
|
||||||
|
},
|
||||||
code(code, lang, escaped) {
|
code(code, lang, escaped) {
|
||||||
return `
|
return `
|
||||||
<div class="relative code-block group/item">
|
<div class="relative code-block group/item">
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
|
||||||
import { Model } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
|
|
||||||
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
||||||
@ -8,33 +7,45 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelList: React.FC<Props> = ({ models }) => {
|
const ExploreModelList: React.FC<Props> = ({ models }) => {
|
||||||
const sortOrder: Record<string, number> = {
|
const takenModelIds: string[] = []
|
||||||
'7b': 1,
|
const featuredModels = models
|
||||||
'13b': 2,
|
.filter((m) => {
|
||||||
'34b': 3,
|
if (m.metadata.tags.includes('Featured')) {
|
||||||
'70b': 4,
|
takenModelIds.push(m.id)
|
||||||
'120b': 5,
|
return m
|
||||||
'tiny': 6,
|
}
|
||||||
}
|
})
|
||||||
const sortedModels = models?.sort((a, b) => {
|
.sort((m1, m2) => m1.metadata.size - m2.metadata.size)
|
||||||
const aIsFeatured = a.metadata.tags.includes('Featured')
|
|
||||||
const bIsFeatured = b.metadata.tags.includes('Featured')
|
const recommendedModels = models
|
||||||
const aIsRecommended = a.metadata.tags.includes('Recommended')
|
.filter((m) => {
|
||||||
const bIsRecommended = b.metadata.tags.includes('Recommended')
|
if (m.metadata.tags.includes('Recommended')) {
|
||||||
const aNumericTag =
|
takenModelIds.push(m.id)
|
||||||
a.metadata.tags.find((tag) => !!sortOrder[tag.toLowerCase()]) ?? 'Tiny'
|
return m
|
||||||
const bNumericTag =
|
}
|
||||||
b.metadata.tags.find((tag) => !!sortOrder[tag.toLowerCase()]) ?? 'Tiny'
|
})
|
||||||
|
.sort((m1, m2) => m1.metadata.size - m2.metadata.size)
|
||||||
|
|
||||||
|
const openAiModels = models
|
||||||
|
.filter((m) => {
|
||||||
|
if (m.engine === 'openai') {
|
||||||
|
takenModelIds.push(m.id)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.sort((m1: Model, m2: Model) => m1.name.localeCompare(m2.name))
|
||||||
|
|
||||||
|
const remainingModels = models
|
||||||
|
.filter((m) => !takenModelIds.includes(m.id))
|
||||||
|
.sort((m1, m2) => m1.metadata.size - m2.metadata.size)
|
||||||
|
|
||||||
|
const sortedModels: Model[] = [
|
||||||
|
...featuredModels,
|
||||||
|
...recommendedModels,
|
||||||
|
...openAiModels,
|
||||||
|
...remainingModels,
|
||||||
|
]
|
||||||
|
|
||||||
if (aIsFeatured !== bIsFeatured) return aIsFeatured ? -1 : 1
|
|
||||||
if (aNumericTag !== bNumericTag)
|
|
||||||
return (
|
|
||||||
sortOrder[aNumericTag.toLowerCase()] -
|
|
||||||
sortOrder[bNumericTag.toLowerCase()]
|
|
||||||
)
|
|
||||||
if (aIsRecommended !== bIsRecommended) return aIsRecommended ? -1 : 1
|
|
||||||
return a.metadata.size - b.metadata.size
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full flex-shrink-0">
|
<div className="relative h-full w-full flex-shrink-0">
|
||||||
{sortedModels?.map((model) => (
|
{sortedModels?.map((model) => (
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import { openExternalUrl } from '@janhq/core'
|
||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
ScrollArea,
|
ScrollArea,
|
||||||
@ -44,6 +45,10 @@ const ExploreModelsScreen = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const onHowToImportModelClick = () => {
|
||||||
|
openExternalUrl('https://jan.ai/guides/using-models/import-manually/')
|
||||||
|
}
|
||||||
|
|
||||||
if (loading) return <Loader description="loading ..." />
|
if (loading) return <Loader description="loading ..." />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -72,13 +77,12 @@ const ExploreModelsScreen = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-center">
|
<div className="mt-2 text-center">
|
||||||
<a
|
<p
|
||||||
href="https://jan.ai/guides/using-models/import-manually/"
|
onClick={onHowToImportModelClick}
|
||||||
target="_blank"
|
className="cursor-pointer font-semibold text-white underline"
|
||||||
className="font-semibold text-white underline"
|
|
||||||
>
|
>
|
||||||
How to manually import models
|
How to manually import models
|
||||||
</a>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user