Merge branch 'main' into docs/add-guides

This commit is contained in:
Hieu 2024-01-05 09:35:55 +07:00 committed by GitHub
commit f5b5dfd9b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 428 additions and 111 deletions

View File

@ -70,25 +70,25 @@ 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/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" />
<b>jan.exe</b>
</a>
</td>
<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" />
<b>Intel</b>
</a>
</td>
<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" />
<b>M1/M2</b>
</a>
</td>
<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" />
<b>jan.deb</b>
</a>

60
USAGE.md Normal file
View 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). ![](/docs/static/img/usage/jan-gpu-enable-setting.png)
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.
![](/docs/static/img/usage/jan-open-home-directory.png)
![](/docs/static/img/usage/jan-open-settings-1.png)
![](/docs/static/img/usage/jan-open-settings-2.png)
![](/docs/static/img/usage/jan-open-settings-3.png)
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

View File

@ -53,9 +53,10 @@ export enum FileSystemRoute {
writeFileSync = 'writeFileSync',
}
export enum FileManagerRoute {
synceFile = 'syncFile',
syncFile = 'syncFile',
getUserSpace = 'getUserSpace',
getResourcePath = 'getResourcePath',
fileStat = 'fileStat',
}
export type ApiFunction = (...args: any[]) => any

View File

@ -1,3 +1,5 @@
import { FileStat } from './types'
/**
* 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()
/**
* 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
*/
@ -97,4 +108,5 @@ export {
joinPath,
openExternalUrl,
baseName,
fileStat,
}

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

View File

@ -0,0 +1,4 @@
export type FileStat = {
isDirectory: boolean
size: number
}

View File

@ -4,3 +4,4 @@ export * from './thread'
export * from './message'
export * from './inference'
export * from './monitoring'
export * from './file'

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@ -4,14 +4,17 @@ import reflect from '@alumna/reflect'
import { FileManagerRoute } from '@janhq/core'
import { userSpacePath, getResourcePath } from './../utils/path'
import fs from 'fs'
import { join } from 'path'
import { FileStat } from '@janhq/core/.'
/**
* Handles file system extensions operations.
*/
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(
FileManagerRoute.synceFile,
FileManagerRoute.syncFile,
async (_event, src: string, dest: string) => {
return reflect({
src,
@ -31,7 +34,33 @@ export function handleFileMangerIPCs() {
)
// Handles the 'getResourcePath' IPC event. This event is triggered to get the resource path.
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) => {
return getResourcePath()
})
ipcMain.handle(FileManagerRoute.getResourcePath, async (_event) =>
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
}
)
}

View File

@ -1 +1 @@
0.1.32
0.1.34

View File

@ -1,3 +1,3 @@
@echo off
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

View File

@ -8,7 +8,7 @@
"license": "AGPL-3.0",
"scripts": {
"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:win32": "download.bat",
"downloadnitro": "run-script-os",

View File

@ -30,7 +30,10 @@ export function requestInference(
signal: controller?.signal,
})
.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 decoder = new TextDecoder("utf-8");
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();
})

View File

@ -85,28 +85,40 @@ function checkFileExistenceInPaths(file: string, paths: string[]): boolean {
}
function updateCudaExistence() {
let files: string[];
let filesCuda12: string[];
let filesCuda11: string[];
let paths: string[];
let cudaVersion: string = "";
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) : [];
const nitro_cuda_path = path.join(__dirname, "bin", "win-cuda");
paths.push(nitro_cuda_path);
} 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
? 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/");
}
let cudaExists = files.every(
let cudaExists = filesCuda12.every(
(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;
try {
data = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
@ -115,6 +127,7 @@ function updateCudaExistence() {
}
data["cuda"].exist = cudaExists;
data["cuda"].version = cudaVersion;
if (cudaExists) {
data.run_mode = "gpu";
}
@ -376,12 +389,17 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
let cudaVisibleDevices = "";
let binaryName;
if (process.platform === "win32") {
let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
if (nvida_info["run_mode"] === "cpu") {
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
if (nvidiaInfo["run_mode"] === "cpu") {
binaryFolder = path.join(binaryFolder, "win-cpu");
} else {
binaryFolder = path.join(binaryFolder, "win-cuda");
cudaVisibleDevices = nvida_info["gpu_highest_vram"];
if (nvidiaInfo["cuda"].version === "12") {
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";
} else if (process.platform === "darwin") {
@ -392,12 +410,17 @@ function spawnNitroProcess(nitroResourceProbe: any): Promise<any> {
}
binaryName = "nitro";
} else {
let nvida_info = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
if (nvida_info["run_mode"] === "cpu") {
let nvidiaInfo = JSON.parse(readFileSync(NVIDIA_INFO_FILE, "utf-8"));
if (nvidiaInfo["run_mode"] === "cpu") {
binaryFolder = path.join(binaryFolder, "linux-cpu");
} else {
binaryFolder = path.join(binaryFolder, "linux-cuda");
cudaVisibleDevices = nvida_info["gpu_highest_vram"];
if (nvidiaInfo["cuda"].version === "12") {
binaryFolder = path.join(binaryFolder, "linux-cuda-12-0");
}
else {
binaryFolder = path.join(binaryFolder, "linux-cuda-11-4");
}
cudaVisibleDevices = nvidiaInfo["gpu_highest_vram"];
}
binaryName = "nitro";
}

View File

@ -46,7 +46,10 @@ export function requestInference(
subscriber.complete();
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 decoder = new TextDecoder("utf-8");
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();
})

View File

@ -5,11 +5,12 @@ import {
abortDownload,
getResourcePath,
getUserSpace,
fileStat,
InferenceEngine,
joinPath,
ModelExtension,
Model,
} from '@janhq/core'
import { ModelExtension, Model } from '@janhq/core'
import { baseName } from '@janhq/core/.'
/**
* A extension for models
@ -21,6 +22,9 @@ export default class JanModelExtension implements ModelExtension {
private static readonly _incompletedModelFileName = '.download'
private static readonly _offlineInferenceEngine = InferenceEngine.nitro
private static readonly _configDirName = 'config'
private static readonly _defaultModelFileName = 'default-model.json'
/**
* Implements type from JanExtension.
* @override
@ -199,7 +203,7 @@ export default class JanModelExtension implements ModelExtension {
): Promise<Model[]> {
try {
if (!(await fs.existsSync(JanModelExtension._homeDir))) {
console.debug('model folder not found')
console.error('Model folder not found')
return []
}
@ -220,13 +224,22 @@ export default class JanModelExtension implements ModelExtension {
dirName,
JanModelExtension._modelMetadataFileName,
])
let model = await this.readModelMetadata(jsonPath)
model = typeof model === 'object' ? model : JSON.parse(model)
if (selector && !(await selector?.(dirName, model))) {
return
if (await fs.existsSync(jsonPath)) {
// 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 modelData = results.map((result) => {
@ -254,6 +267,84 @@ export default class JanModelExtension implements ModelExtension {
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.
* @returns A Promise that resolves with an array of all models.

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

View File

@ -17,6 +17,7 @@
"build": "tsc"
},
"dependencies": {
"@alumna/reflect": "^1.1.3",
"@fastify/cors": "^8.4.2",
"@fastify/static": "^6.12.0",
"@fastify/swagger": "^8.13.0",

View File

@ -36,28 +36,23 @@ const GPUDriverPrompt: React.FC = () => {
<Modal open={showNotification} onOpenChange={openChanged}>
<ModalContent>
<ModalHeader>
<ModalTitle>Missing Nvidia Driver and Cuda Toolkit</ModalTitle>
<ModalTitle>
Checking for machine that does not meet the requirements.
</ModalTitle>
</ModalHeader>
<p>
It seems like you are missing Nvidia Driver or Cuda Toolkit or both.
Please follow the instructions on the{' '}
It appears that you are missing some dependencies required to run in
GPU mode. Please follow the instructions below for more details{' '}
<span
className="cursor-pointer text-blue-600"
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>{' '}
and the{' '}
<span
className="cursor-pointer text-blue-600"
onClick={() =>
openExternalUrl('https://www.nvidia.com/Download/index.aspx')
}
>
Nvidia Driver Installation Page
</span>
.
</p>
<div className="flex items-center space-x-2">

View File

@ -22,8 +22,12 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
const modelsRef = useRef(models)
const { setDownloadedModels, downloadedModels } = useGetDownloadedModels()
const { setDownloadState, setDownloadStateSuccess, setDownloadStateFailed } =
useDownloadState()
const {
setDownloadState,
setDownloadStateSuccess,
setDownloadStateFailed,
setDownloadStateCancelled,
} = useDownloadState()
const downloadedModelRef = useRef(downloadedModels)
useEffect(() => {
@ -52,13 +56,18 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
window.electronAPI.onFileDownloadError(
async (_event: string, state: any) => {
if (state.err?.message !== 'aborted')
console.error('Download error', state)
const modelName = await baseName(state.fileName)
const model = modelsRef.current.find(
(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)
}
}
}
)

View File

@ -29,26 +29,49 @@ const setDownloadStateSuccessAtom = atom(null, (get, set, modelId: string) => {
})
})
const setDownloadStateFailedAtom = 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
const setDownloadStateFailedAtom = atom(
null,
(get, set, modelId: string, error: string) => {
const currentState = { ...get(modelDownloadStateAtom) }
const state = currentState[modelId]
if (!state) {
console.debug(`Cannot find download state for ${modelId}`)
toaster({
title: 'Download Failed',
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() {
const modelDownloadState = useAtomValue(modelDownloadStateAtom)
const setDownloadState = useSetAtom(setDownloadStateAtom)
const setDownloadStateSuccess = useSetAtom(setDownloadStateSuccessAtom)
const setDownloadStateFailed = useSetAtom(setDownloadStateFailedAtom)
const setDownloadStateCancelled = useSetAtom(setDownloadStateCancelledAtom)
const downloadStates: DownloadState[] = []
for (const [, value] of Object.entries(modelDownloadState)) {
@ -61,6 +84,7 @@ export function useDownloadState() {
setDownloadState,
setDownloadStateSuccess,
setDownloadStateFailed,
setDownloadStateCancelled,
downloadStates,
}
}

View File

@ -5,7 +5,7 @@ import { ChatCompletionRole, MessageStatus, ThreadMessage } from '@janhq/core'
import hljs from 'highlight.js'
import { useAtomValue } from 'jotai'
import { Marked } from 'marked'
import { Marked, Renderer } from 'marked'
import { markedHighlight } from 'marked-highlight'
@ -30,7 +30,7 @@ const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
}
const clipboard = useClipboard({ timeout: 1000 })
const marked = new Marked(
const marked: Marked = new Marked(
markedHighlight({
langPrefix: 'hljs',
highlight(code, lang) {
@ -46,6 +46,11 @@ const SimpleTextMessage: React.FC<ThreadMessage> = (props) => {
}),
{
renderer: {
link: (href, title, text) => {
return Renderer.prototype.link
?.apply(this, [href, title, text])
.replace('<a', "<a target='_blank'")
},
code(code, lang, escaped) {
return `
<div class="relative code-block group/item">

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Model } from '@janhq/core'
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
@ -8,33 +7,45 @@ type Props = {
}
const ExploreModelList: React.FC<Props> = ({ models }) => {
const sortOrder: Record<string, number> = {
'7b': 1,
'13b': 2,
'34b': 3,
'70b': 4,
'120b': 5,
'tiny': 6,
}
const sortedModels = models?.sort((a, b) => {
const aIsFeatured = a.metadata.tags.includes('Featured')
const bIsFeatured = b.metadata.tags.includes('Featured')
const aIsRecommended = a.metadata.tags.includes('Recommended')
const bIsRecommended = b.metadata.tags.includes('Recommended')
const aNumericTag =
a.metadata.tags.find((tag) => !!sortOrder[tag.toLowerCase()]) ?? 'Tiny'
const bNumericTag =
b.metadata.tags.find((tag) => !!sortOrder[tag.toLowerCase()]) ?? 'Tiny'
const takenModelIds: string[] = []
const featuredModels = models
.filter((m) => {
if (m.metadata.tags.includes('Featured')) {
takenModelIds.push(m.id)
return m
}
})
.sort((m1, m2) => m1.metadata.size - m2.metadata.size)
const recommendedModels = models
.filter((m) => {
if (m.metadata.tags.includes('Recommended')) {
takenModelIds.push(m.id)
return m
}
})
.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 (
<div className="relative h-full w-full flex-shrink-0">
{sortedModels?.map((model) => (

View File

@ -1,5 +1,6 @@
import { useState } from 'react'
import { openExternalUrl } from '@janhq/core'
import {
Input,
ScrollArea,
@ -44,6 +45,10 @@ const ExploreModelsScreen = () => {
}
})
const onHowToImportModelClick = () => {
openExternalUrl('https://jan.ai/guides/using-models/import-manually/')
}
if (loading) return <Loader description="loading ..." />
return (
@ -72,13 +77,12 @@ const ExploreModelsScreen = () => {
/>
</div>
<div className="mt-2 text-center">
<a
href="https://jan.ai/guides/using-models/import-manually/"
target="_blank"
className="font-semibold text-white underline"
<p
onClick={onHowToImportModelClick}
className="cursor-pointer font-semibold text-white underline"
>
How to manually import models
</a>
</p>
</div>
</div>
</div>