Skip to content

Commit

Permalink
Feature GPU detection for Jan on Windows and Linux (janhq#1242)
Browse files Browse the repository at this point in the history
* Add js function to generate gpu and cuda detection

* inference nitro manage via json file instead of bash and bat script

* Add /usr/lib/x86_64-linux-gnu/ to linux check gpu

* chore: add CPU - GPU toggle

* correct file path

* fix: exist file sync check

* fix: get resources path

* Fix error jan/engines create existed error

* Seting sync to file

* Fix error show notification for GPU

* Set notify default to true

---------

Co-authored-by: Hien To <[email protected]>
Co-authored-by: Louis <[email protected]>
  • Loading branch information
3 people authored Dec 29, 2023
1 parent 19f583c commit 1ec8174
Show file tree
Hide file tree
Showing 22 changed files with 503 additions and 64 deletions.
3 changes: 2 additions & 1 deletion core/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ export enum AppRoute {
openAppDirectory = 'openAppDirectory',
openFileExplore = 'openFileExplorer',
relaunch = 'relaunch',
joinPath = 'joinPath'
joinPath = 'joinPath',
baseName = 'baseName',
}

export enum AppEvent {
Expand Down
23 changes: 23 additions & 0 deletions core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,27 @@ const openFileExplorer: (path: string) => Promise<any> = (path) =>
*/
const joinPath: (paths: string[]) => Promise<string> = (paths) => global.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)

/**
* Opens an external URL in the default web browser.
*
* @param {string} url - The URL to open.
* @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)

/**
* 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()

/**
Expand All @@ -74,4 +95,6 @@ export {
openFileExplorer,
getResourcePath,
joinPath,
openExternalUrl,
baseName,
}
8 changes: 6 additions & 2 deletions core/src/node/api/routes/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppRoute } from '../../../api'
import { HttpServer } from '../HttpServer'
import { join } from 'path'
import { basename, join } from 'path'
import {
chatCompletions,
deleteBuilder,
Expand Down Expand Up @@ -36,7 +36,11 @@ export const commonRouter = async (app: HttpServer) => {
// App Routes
app.post(`/app/${AppRoute.joinPath}`, async (request: any, reply: any) => {
const args = JSON.parse(request.body) as any[]
console.debug('joinPath: ', ...args[0])
reply.send(JSON.stringify(join(...args[0])))
})

app.post(`/app/${AppRoute.baseName}`, async (request: any, reply: any) => {
const args = JSON.parse(request.body) as any[]
reply.send(JSON.stringify(basename(args[0])))
})
}
15 changes: 9 additions & 6 deletions electron/handlers/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { app, ipcMain, shell, nativeTheme } from 'electron'
import { join } from 'path'
import { join, basename } from 'path'
import { WindowManager } from './../managers/window'
import { userSpacePath } from './../utils/path'
import { AppRoute } from '@janhq/core'
import { getResourcePath } from './../utils/path'
import {
ExtensionManager,
ModuleManager,
} from '@janhq/core/node'
import { ExtensionManager, ModuleManager } from '@janhq/core/node'

export function handleAppIPCs() {
/**
Expand Down Expand Up @@ -53,6 +49,13 @@ export function handleAppIPCs() {
join(...paths)
)

/**
* Retrieve basename from given path, respect to the current OS.
*/
ipcMain.handle(AppRoute.baseName, async (_event, path: string) =>
basename(path)
)

/**
* Relaunches the app in production - reload window in development.
* @param _event - The IPC event object.
Expand Down
7 changes: 5 additions & 2 deletions electron/handlers/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ export function handleDownloaderIPCs() {
*/
ipcMain.handle(DownloadRoute.downloadFile, async (_event, url, fileName) => {
const userDataPath = join(app.getPath('home'), 'jan')
if (typeof fileName === 'string' && fileName.includes('file:/')) {
fileName = fileName.replace('file:/', '')
if (
typeof fileName === 'string' &&
(fileName.includes('file:/') || fileName.includes('file:\\'))
) {
fileName = fileName.replace('file:/', '').replace('file:\\', '')
}
const destination = resolve(userDataPath, fileName)
const rq = request(url)
Expand Down
4 changes: 2 additions & 2 deletions electron/handlers/fileManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ipcMain } from 'electron'
// @ts-ignore
import reflect from '@alumna/reflect'

import { FileManagerRoute, getResourcePath } from '@janhq/core'
import { userSpacePath } from './../utils/path'
import { FileManagerRoute } from '@janhq/core'
import { userSpacePath, getResourcePath } from './../utils/path'

/**
* Handles file system extensions operations.
Expand Down
12 changes: 10 additions & 2 deletions electron/handlers/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,16 @@ export function handleFsIPCs() {
return import(moduleName).then((mdl) =>
mdl[route](
...args.map((arg) =>
typeof arg === 'string' && arg.includes('file:/')
? join(userSpacePath, arg.replace('file:/', ''))
typeof arg === 'string' &&
(arg.includes(`file:/`) || arg.includes(`file:\\`))
? join(
userSpacePath,
arg
.replace(`file://`, '')
.replace(`file:/`, '')
.replace(`file:\\\\`, '')
.replace(`file:\\`, '')
)
: arg
)
)
Expand Down
2 changes: 1 addition & 1 deletion extensions/conversational-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class JSONConversationalExtension
*/
async onLoad() {
if (!(await fs.existsSync(JSONConversationalExtension._homeDir)))
fs.mkdirSync(JSONConversationalExtension._homeDir)
await fs.mkdirSync(JSONConversationalExtension._homeDir)
console.debug('JSONConversationalExtension loaded')
}

Expand Down
31 changes: 27 additions & 4 deletions extensions/inference-nitro-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import { join } from "path";
* It also subscribes to events emitted by the @janhq/core package and handles new message requests.
*/
export default class JanInferenceNitroExtension implements InferenceExtension {
private static readonly _homeDir = "engines";
private static readonly _homeDir = "file://engines";
private static readonly _settingsDir = "file://settings";
private static readonly _engineMetadataFileName = "nitro.json";

private static _currentModel: Model;
Expand All @@ -58,9 +59,13 @@ export default class JanInferenceNitroExtension implements InferenceExtension {
/**
* Subscribes to events emitted by the @janhq/core package.
*/
async onLoad() {
if (!(await fs.existsSync(JanInferenceNitroExtension._homeDir)))
fs.mkdirSync(JanInferenceNitroExtension._homeDir);
async onLoad(): Promise<void> {
if (!(await fs.existsSync(JanInferenceNitroExtension._homeDir))) {
await fs.mkdirSync(JanInferenceNitroExtension._homeDir).catch((err) => console.debug(err));
}

if (!(await fs.existsSync(JanInferenceNitroExtension._settingsDir)))
await fs.mkdirSync(JanInferenceNitroExtension._settingsDir);
this.writeDefaultEngineSettings();

// Events subscription
Expand All @@ -79,6 +84,24 @@ export default class JanInferenceNitroExtension implements InferenceExtension {
events.on(EventName.OnInferenceStopped, () => {
JanInferenceNitroExtension.handleInferenceStopped(this);
});

// Attempt to fetch nvidia info
await executeOnMain(MODULE, "updateNvidiaInfo", {});

const gpuDriverConf = await fs.readFileSync(
join(JanInferenceNitroExtension._settingsDir, "settings.json")
);
if (gpuDriverConf.notify && gpuDriverConf.run_mode === "cpu") {
// Driver is fully installed, but not in use
if (gpuDriverConf.nvidia_driver?.exist && gpuDriverConf.cuda?.exist) {
events.emit("OnGPUCompatiblePrompt", {});
// Prompt user to switch
} else if (gpuDriverConf.nvidia_driver?.exist) {
// Prompt user to install cuda toolkit
events.emit("OnGPUDriverMissingPrompt", {});
}
}
Promise.resolve()
}

/**
Expand Down
Loading

0 comments on commit 1ec8174

Please sign in to comment.