diff --git a/core/src/browser/extension.ts b/core/src/browser/extension.ts index 13f0e94f3d..b27afe0238 100644 --- a/core/src/browser/extension.ts +++ b/core/src/browser/extension.ts @@ -23,9 +23,9 @@ export interface Compatibility { 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. + 'NotCompatible', // require but not compatible. ] as const export type InstallationStateTuple = typeof ALL_INSTALLATION_STATE @@ -98,13 +98,6 @@ export abstract class BaseExtension implements ExtensionType { return undefined } - /** - * Determine if the extension is updatable. - */ - updatable(): boolean { - return false - } - async registerSettings(settings: SettingComponentProps[]): Promise { if (!this.name) { console.error('Extension name is not defined') diff --git a/extensions/inference-nitro-extension/package.json b/extensions/inference-nitro-extension/package.json index 98f8cde944..4ea1446ac5 100644 --- a/extensions/inference-nitro-extension/package.json +++ b/extensions/inference-nitro-extension/package.json @@ -1,7 +1,7 @@ { "name": "@janhq/inference-nitro-extension", "version": "1.0.0", - "description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See nitro.jan.ai", + "description": "This extension embeds Nitro, a lightweight (3mb) inference engine written in C++. See https://nitro.jan.ai.\nUse this setting if you encounter errors related to **CUDA toolkit** during application execution.", "main": "dist/index.js", "node": "dist/node/index.cjs.js", "author": "Jan ", @@ -29,6 +29,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-replace": "^5.0.5", + "@types/decompress": "^4.2.7", "@types/jest": "^29.5.12", "@types/node": "^20.11.4", "@types/os-utils": "^0.0.4", @@ -47,6 +48,7 @@ }, "dependencies": { "@janhq/core": "file:../../core", + "decompress": "^4.2.1", "fetch-retry": "^5.0.6", "path-browserify": "^1.0.1", "rxjs": "^7.8.1", @@ -65,6 +67,7 @@ "bundleDependencies": [ "tcp-port-used", "fetch-retry", - "@janhq/core" + "@janhq/core", + "decompress" ] } diff --git a/extensions/inference-nitro-extension/rollup.config.ts b/extensions/inference-nitro-extension/rollup.config.ts index 5af1f0c518..7b2758881a 100644 --- a/extensions/inference-nitro-extension/rollup.config.ts +++ b/extensions/inference-nitro-extension/rollup.config.ts @@ -92,6 +92,9 @@ export default [ JAN_SERVER_INFERENCE_URL: JSON.stringify( 'http://localhost:1337/v1/chat/completions' ), + CUDA_DOWNLOAD_URL: JSON.stringify( + 'https://catalog.jan.ai/dist/cuda-dependencies///cuda.tar.gz' + ), }), // Allow json resolution json(), diff --git a/extensions/inference-nitro-extension/src/index.ts b/extensions/inference-nitro-extension/src/index.ts index 7603269edd..67324d5346 100644 --- a/extensions/inference-nitro-extension/src/index.ts +++ b/extensions/inference-nitro-extension/src/index.ts @@ -12,8 +12,19 @@ import { Model, ModelEvent, LocalOAIEngine, + InstallationState, + systemInformation, + fs, + getJanDataFolderPath, + joinPath, + DownloadRequest, + baseName, + downloadFile, + DownloadState, + DownloadEvent, } from '@janhq/core' +declare const CUDA_DOWNLOAD_URL: string /** * A class that implements the InferenceExtension interface from the @janhq/core package. * The class provides methods for initializing and stopping a model, and for making inference requests. @@ -61,6 +72,11 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { const models = MODELS as unknown as Model[] this.registerModels(models) super.onLoad() + + executeOnMain(NODE, 'addAdditionalDependencies', { + name: this.name, + version: this.version, + }) } /** @@ -96,4 +112,80 @@ export default class JanInferenceNitroExtension extends LocalOAIEngine { } return super.unloadModel(model) } + + override async install(): Promise { + const info = await systemInformation() + + const platform = info.osInfo?.platform === 'win32' ? 'windows' : 'linux' + const downloadUrl = CUDA_DOWNLOAD_URL + + const url = downloadUrl + .replace('', info.gpuSetting.cuda?.version ?? '12.4') + .replace('', platform) + + console.debug('Downloading Cuda Toolkit Dependency: ', url) + + const janDataFolderPath = await getJanDataFolderPath() + + const executableFolderPath = await joinPath([ + janDataFolderPath, + 'engines', + this.name ?? 'nitro', + this.version ?? '1.0.0', + ]) + + if (!(await fs.existsSync(executableFolderPath))) { + await fs.mkdir(executableFolderPath) + } + + const tarball = await baseName(url) + const tarballFullPath = await joinPath([executableFolderPath, tarball]) + + const downloadRequest: DownloadRequest = { + url, + localPath: tarballFullPath, + extensionId: this.name, + downloadType: 'extension', + } + downloadFile(downloadRequest) + + const onFileDownloadSuccess = async (state: DownloadState) => { + console.log(state) + // if other download, ignore + if (state.fileName !== tarball) return + events.off(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) + await executeOnMain( + NODE, + 'decompressRunner', + tarballFullPath, + executableFolderPath + ) + events.emit(DownloadEvent.onFileUnzipSuccess, state) + } + events.on(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) + } + + override async installationState(): Promise { + const info = await systemInformation() + if ( + info.gpuSetting.run_mode === 'gpu' && + !info.gpuSetting.vulkan && + info.osInfo && + info.osInfo.platform !== 'darwin' && + !info.gpuSetting.cuda?.exist + ) { + const janDataFolderPath = await getJanDataFolderPath() + + const executableFolderPath = await joinPath([ + janDataFolderPath, + 'engines', + this.name ?? 'nitro', + this.version ?? '1.0.0', + ]) + + if (!(await fs.existsSync(executableFolderPath))) return 'NotInstalled' + return 'Installed' + } + return 'NotRequired' + } } diff --git a/extensions/inference-nitro-extension/src/node/index.ts b/extensions/inference-nitro-extension/src/node/index.ts index 7cde94f79d..3d742721e2 100644 --- a/extensions/inference-nitro-extension/src/node/index.ts +++ b/extensions/inference-nitro-extension/src/node/index.ts @@ -11,9 +11,11 @@ import { ModelSettingParams, PromptTemplate, SystemInformation, + getJanDataFolderPath, } from '@janhq/core/node' import { executableNitroFile } from './execute' import terminate from 'terminate' +import decompress from 'decompress' // Polyfill fetch with retry const fetchRetry = fetchRT(fetch) @@ -420,9 +422,32 @@ const getCurrentNitroProcessInfo = (): NitroProcessInfo => { } } +const addAdditionalDependencies = (data: { name: string; version: string }) => { + const additionalPath = path.delimiter.concat( + path.join(getJanDataFolderPath(), 'engines', data.name, data.version) + ) + // Set the updated PATH + process.env.PATH = (process.env.PATH || '').concat(additionalPath) + process.env.LD_LIBRARY_PATH = (process.env.LD_LIBRARY_PATH || '').concat( + additionalPath + ) +} + +const decompressRunner = async (zipPath: string, output: string) => { + console.debug(`Decompressing ${zipPath} to ${output}...`) + try { + const files = await decompress(zipPath, output) + console.debug('Decompress finished!', files) + } catch (err) { + console.error(`Decompress ${zipPath} failed: ${err}`) + } +} + export default { loadModel, unloadModel, dispose, getCurrentNitroProcessInfo, + addAdditionalDependencies, + decompressRunner, } diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index 9749e6a530..9dd1068680 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -85,8 +85,8 @@ export default class JanModelExtension extends ModelExtension { } if (!JanModelExtension._supportedGpuArch.includes(gpuArch)) { - console.error( - `Your GPU: ${firstGpu} is not supported. Only 20xx, 30xx, 40xx series are supported.` + console.debug( + `Your GPU: ${JSON.stringify(firstGpu)} is not supported. Only 30xx, 40xx series are supported.` ) return } diff --git a/extensions/monitoring-extension/src/node/index.ts b/extensions/monitoring-extension/src/node/index.ts index 26a21ad490..bb0c4ac182 100644 --- a/extensions/monitoring-extension/src/node/index.ts +++ b/extensions/monitoring-extension/src/node/index.ts @@ -200,7 +200,7 @@ const updateGpuInfo = async () => process.platform === 'win32' ? `${__dirname}\\..\\bin\\vulkaninfoSDK.exe --summary` : `${__dirname}/../bin/vulkaninfo --summary`, - (error, stdout) => { + async (error, stdout) => { if (!error) { const output = stdout.toString() @@ -221,7 +221,7 @@ const updateGpuInfo = async () => data.gpus_in_use = [data.gpus.length > 1 ? '1' : '0'] } - data = updateCudaExistence(data) + data = await updateCudaExistence(data) writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2)) log(`[APP]::${JSON.stringify(data)}`) resolve({}) @@ -233,7 +233,7 @@ const updateGpuInfo = async () => } else { exec( 'nvidia-smi --query-gpu=index,memory.total,name --format=csv,noheader,nounits', - (error, stdout) => { + async (error, stdout) => { if (!error) { log(`[SPECS]::${stdout}`) // Get GPU info and gpu has higher memory first @@ -264,7 +264,8 @@ const updateGpuInfo = async () => data.gpus_in_use = [data.gpu_highest_vram] } - data = updateCudaExistence(data) + data = await updateCudaExistence(data) + console.log(data) writeFileSync(GPU_INFO_FILE, JSON.stringify(data, null, 2)) log(`[APP]::${JSON.stringify(data)}`) resolve({}) @@ -283,9 +284,9 @@ const checkFileExistenceInPaths = (file: string, paths: string[]): boolean => { /** * Validate cuda for linux and windows */ -const updateCudaExistence = ( +const updateCudaExistence = async ( data: GpuSetting = DEFAULT_SETTINGS -): GpuSetting => { +): Promise => { let filesCuda12: string[] let filesCuda11: string[] let paths: string[] @@ -329,6 +330,23 @@ const updateCudaExistence = ( } data.is_initial = false + + // Attempt to query CUDA using NVIDIA SMI + if (!cudaExists) { + await new Promise((resolve, reject) => { + exec('nvidia-smi', (error, stdout) => { + if (!error) { + const regex = /CUDA\s*Version:\s*(\d+\.\d+)/g + const match = regex.exec(stdout) + if (match && match[1]) { + data.cuda.version = match[1] + } + } + console.log(data) + resolve() + }) + }) + } return data } diff --git a/extensions/tensorrt-llm-extension/src/index.ts b/extensions/tensorrt-llm-extension/src/index.ts index a02f30dc21..c7842eefd5 100644 --- a/extensions/tensorrt-llm-extension/src/index.ts +++ b/extensions/tensorrt-llm-extension/src/index.ts @@ -22,6 +22,7 @@ import { MessageRequest, ModelEvent, getJanDataFolderPath, + SystemInformation, } from '@janhq/core' /** @@ -40,7 +41,6 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { private supportedGpuArch = ['ampere', 'ada'] private supportedPlatform = ['win32', 'linux'] - private isUpdateAvailable = false override compatibility() { return COMPATIBILITY as unknown as Compatibility @@ -59,33 +59,8 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { await this.removePopulatedModels() const info = await systemInformation() - console.debug( - `TensorRTLLMExtension installing pre-requisites... ${JSON.stringify(info)}` - ) - const gpuSetting: GpuSetting | undefined = info.gpuSetting - if (gpuSetting === undefined || gpuSetting.gpus.length === 0) { - console.error('No GPU setting found. Please check your GPU setting.') - return - } - - // TODO: we only check for the first graphics card. Need to refactor this later. - const firstGpu = gpuSetting.gpus[0] - if (!firstGpu.name.toLowerCase().includes('nvidia')) { - console.error('No Nvidia GPU found. Please check your GPU setting.') - return - } - if (firstGpu.arch === undefined) { - console.error('No GPU architecture found. Please check your GPU setting.') - return - } - - if (!this.supportedGpuArch.includes(firstGpu.arch)) { - console.error( - `Your GPU: ${firstGpu} is not supported. Only 20xx, 30xx, 40xx series are supported.` - ) - return - } + if (!this.isCompatible(info)) return const janDataFolderPath = await getJanDataFolderPath() const engineVersion = TENSORRT_VERSION @@ -95,7 +70,7 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { 'engines', this.provider, engineVersion, - firstGpu.arch, + info.gpuSetting.gpus[0].arch, ]) if (!(await fs.existsSync(executableFolderPath))) { @@ -107,7 +82,7 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { const url = placeholderUrl .replace(//g, tensorrtVersion) - .replace(//g, firstGpu.arch) + .replace(//g, info.gpuSetting!.gpus[0]!.arch!) const tarball = await baseName(url) @@ -163,70 +138,17 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { } override async loadModel(model: Model): Promise { - if (model.engine !== this.provider) return - if ((await this.installationState()) === 'Installed') return super.loadModel(model) - else { - events.emit(ModelEvent.OnModelFail, { - ...model, - error: { - message: 'EXTENSION_IS_NOT_INSTALLED::TensorRT-LLM extension', - }, - }) - } - } - override updatable() { - return this.isUpdateAvailable + throw new Error('EXTENSION_IS_NOT_INSTALLED::TensorRT-LLM extension') } override async installationState(): Promise { const info = await systemInformation() - const gpuSetting: GpuSetting | undefined = info.gpuSetting - if (gpuSetting === undefined) { - console.warn( - 'No GPU setting found. TensorRT-LLM extension is not installed' - ) - return 'NotInstalled' // TODO: maybe disabled / incompatible is more appropriate - } - - if (gpuSetting.gpus.length === 0) { - console.warn('No GPU found. TensorRT-LLM extension is not installed') - return 'NotInstalled' - } - - const firstGpu = gpuSetting.gpus[0] - if (!firstGpu.name.toLowerCase().includes('nvidia')) { - console.error('No Nvidia GPU found. Please check your GPU setting.') - return 'NotInstalled' - } - - if (firstGpu.arch === undefined) { - console.error('No GPU architecture found. Please check your GPU setting.') - return 'NotInstalled' - } - - if (!this.supportedGpuArch.includes(firstGpu.arch)) { - console.error( - `Your GPU: ${firstGpu} is not supported. Only 20xx, 30xx, 40xx series are supported.` - ) - return 'NotInstalled' - } - - const osInfo = info.osInfo - if (!osInfo) { - console.error('No OS information found. Please check your OS setting.') - return 'NotInstalled' - } - - if (!this.supportedPlatform.includes(osInfo.platform)) { - console.error( - `Your OS: ${osInfo.platform} is not supported. Only Windows and Linux are supported.` - ) - return 'NotInstalled' - } + if (!this.isCompatible(info)) return 'NotCompatible' + const firstGpu = info.gpuSetting.gpus[0] const janDataFolderPath = await getJanDataFolderPath() const engineVersion = TENSORRT_VERSION @@ -236,7 +158,7 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { this.provider, engineVersion, firstGpu.arch, - osInfo.platform === 'win32' ? 'nitro.exe' : 'nitro', + info.osInfo.platform === 'win32' ? 'nitro.exe' : 'nitro', ]) // For now, we just check the executable of nitro x tensor rt @@ -258,4 +180,19 @@ export default class TensorRTLLMExtension extends LocalOAIEngine { if (data.model) data.model.parameters.stream = true super.inference(data) } + + isCompatible(info: SystemInformation): info is Required & { + gpuSetting: { gpus: { arch: string }[] } + } { + const firstGpu = info.gpuSetting.gpus[0] + return ( + !!info.osInfo && + info.gpuSetting?.gpus?.length > 0 && + this.supportedPlatform.includes(info.osInfo.platform) && + !!firstGpu && + !!firstGpu.arch && + firstGpu.name.toLowerCase().includes('nvidia') && + this.supportedGpuArch.includes(firstGpu.arch) + ) + } } diff --git a/web/containers/Layout/BottomBar/InstallingExtension/index.tsx b/web/containers/Layout/BottomBar/InstallingExtension/index.tsx index 05e8038813..0d5460955d 100644 --- a/web/containers/Layout/BottomBar/InstallingExtension/index.tsx +++ b/web/containers/Layout/BottomBar/InstallingExtension/index.tsx @@ -34,7 +34,7 @@ const InstallingExtension: React.FC = () => { onClick={onClick} >

- Installing Extension + Installing Additional Dependencies

diff --git a/web/hooks/useSettings.ts b/web/hooks/useSettings.ts index 1930a10e05..8743813173 100644 --- a/web/hooks/useSettings.ts +++ b/web/hooks/useSettings.ts @@ -2,32 +2,30 @@ import { useCallback, useEffect, useState } from 'react' import { fs, joinPath } from '@janhq/core' +type NvidiaDriver = { + exist: boolean + version: string +} + export type AppSettings = { run_mode: 'cpu' | 'gpu' | undefined notify: boolean gpus_in_use: string[] vulkan: boolean gpus: string[] + nvidia_driver: NvidiaDriver + cuda: NvidiaDriver } export const useSettings = () => { - const [isGPUModeEnabled, setIsGPUModeEnabled] = useState(false) // New state for GPU mode const [settings, setSettings] = useState() useEffect(() => { readSettings().then((settings) => setSettings(settings as AppSettings)) - setTimeout(() => validateSettings, 3000) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) - const validateSettings = async () => { - readSettings().then((settings) => { - // Check if run_mode is 'gpu' or 'cpu' and update state accordingly - setIsGPUModeEnabled(settings?.run_mode === 'gpu') - }) - } - const readSettings = useCallback(async () => { if (!window?.core?.api) { return @@ -69,10 +67,8 @@ export const useSettings = () => { } return { - isGPUModeEnabled, readSettings, saveSettings, - validateSettings, settings, } } diff --git a/web/screens/Chat/LoadModelError/index.tsx b/web/screens/Chat/LoadModelError/index.tsx index c2e392ac57..d41090a92e 100644 --- a/web/screens/Chat/LoadModelError/index.tsx +++ b/web/screens/Chat/LoadModelError/index.tsx @@ -9,6 +9,8 @@ import { MainViewState } from '@/constants/screens' import { loadModelErrorAtom } from '@/hooks/useActiveModel' +import { useSettings } from '@/hooks/useSettings' + import { mainViewStateAtom } from '@/helpers/atoms/App.atom' import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' @@ -17,53 +19,97 @@ const LoadModelError = () => { const setModalTroubleShooting = useSetAtom(modalTroubleShootingAtom) const loadModelError = useAtomValue(loadModelErrorAtom) const setMainState = useSetAtom(mainViewStateAtom) - const activeThread = useAtomValue(activeThreadAtom) const setSelectedSettingScreen = useSetAtom(selectedSettingAtom) + const activeThread = useAtomValue(activeThreadAtom) + const { settings } = useSettings() + const PORT_NOT_AVAILABLE = 'PORT_NOT_AVAILABLE' - return ( -
- {loadModelError === PORT_NOT_AVAILABLE ? ( -
-

- Port 3928 is currently unavailable. Check for conflicting apps, or - access  - setModalTroubleShooting(true)} - > - troubleshooting assistance - -  for further support. -

- -
- ) : loadModelError && - typeof loadModelError.includes === 'function' && - loadModelError.includes('EXTENSION_IS_NOT_INSTALLED') ? ( -
-

- Model is currently unavailable. Please switch to a different model - or install the{' '} - {' '} - to continue using it. -

-
- ) : ( -
+ const ErrorMessage = () => { + if (loadModelError === PORT_NOT_AVAILABLE) { + return ( +

+ Port 3928 is currently unavailable. Check for conflicting apps, or + access  + setModalTroubleShooting(true)} + > + troubleshooting assistance + +

+ ) + } else if ( + typeof loadModelError?.includes === 'function' && + loadModelError.includes('EXTENSION_IS_NOT_INSTALLED') + ) { + return ( +

+ Model is currently unavailable. Please switch to a different model or + install the{' '} + { + setMainState(MainViewState.Settings) + if (activeThread?.assistants[0]?.model.engine) { + const engine = EngineManager.instance().get( + activeThread.assistants[0].model.engine + ) + engine?.name && setSelectedSettingScreen(engine.name) + } + }} + > + {loadModelError.split('::')[1] ?? ''} + {' '} + to continue using it. +

+ ) + } else if ( + settings && + settings.run_mode === 'gpu' && + !settings.vulkan && + (!settings.nvidia_driver?.exist || !settings.cuda?.exist) + ) { + return ( + <> + {!settings?.cuda.exist ? ( +

+ The CUDA toolkit may be unavailable. Please use the{' '} + { + setMainState(MainViewState.Settings) + if (activeThread?.assistants[0]?.model.engine) { + const engine = EngineManager.instance().get( + activeThread.assistants[0].model.engine + ) + engine?.name && setSelectedSettingScreen(engine.name) + } + }} + > + Install Additional Dependencies + {' '} + setting to proceed with the download / installation process. +

+ ) : ( +
+ Problem with Nvidia drivers. Please follow the{' '} + + Nvidia Drivers guideline + {' '} + to access installation instructions and ensure proper functioning + of the application. +
+ )} + + ) + } else { + return ( +
Apologies, something’s amiss!

Jan’s in beta. Access  @@ -75,9 +121,19 @@ const LoadModelError = () => {  now.

-
- )} + ) + } + } + + return ( +
+
+

+ +

+ +
) } diff --git a/web/screens/Settings/Advanced/index.tsx b/web/screens/Settings/Advanced/index.tsx index b8312258bb..a9773cd761 100644 --- a/web/screens/Settings/Advanced/index.tsx +++ b/web/screens/Settings/Advanced/index.tsx @@ -67,7 +67,7 @@ const Advanced = () => { const [gpuList, setGpuList] = useState([]) const [gpusInUse, setGpusInUse] = useState([]) - const { readSettings, saveSettings, validateSettings } = useSettings() + const { readSettings, saveSettings } = useSettings() const { stopModel } = useActiveModel() const selectedGpu = gpuList @@ -277,9 +277,6 @@ const Advanced = () => { 'Successfully turned on GPU Acceleration', type: 'success', }) - setTimeout(() => { - validateSettings() - }, 300) } else { saveSettings({ runMode: 'cpu' }) setGpuEnabled(false) diff --git a/web/screens/Settings/CoreExtensions/TensorRtExtensionItem.tsx b/web/screens/Settings/CoreExtensions/ExtensionItem.tsx similarity index 76% rename from web/screens/Settings/CoreExtensions/TensorRtExtensionItem.tsx rename to web/screens/Settings/CoreExtensions/ExtensionItem.tsx index 161ec8bd21..87df381bb9 100644 --- a/web/screens/Settings/CoreExtensions/TensorRtExtensionItem.tsx +++ b/web/screens/Settings/CoreExtensions/ExtensionItem.tsx @@ -1,14 +1,12 @@ import { useCallback, useEffect, useState } from 'react' import { + BaseExtension, Compatibility, - GpuSetting, InstallationState, abortDownload, - systemInformation, } from '@janhq/core' import { - Badge, Button, Progress, Tooltip, @@ -23,25 +21,20 @@ import { useAtomValue } from 'jotai' import { Marked, Renderer } from 'marked' -import UpdateExtensionModal from './UpdateExtensionModal' - import { extensionManager } from '@/extension' -import Extension from '@/extension/Extension' import { installingExtensionAtom } from '@/helpers/atoms/Extension.atom' type Props = { - item: Extension + item: BaseExtension } -const TensorRtExtensionItem: React.FC = ({ item }) => { +const ExtensionItem: React.FC = ({ item }) => { const [compatibility, setCompatibility] = useState( undefined ) const [installState, setInstallState] = useState('NotRequired') const installingExtensions = useAtomValue(installingExtensionAtom) - const [isGpuSupported, setIsGpuSupported] = useState(false) - const [promptUpdateModal, setPromptUpdateModal] = useState(false) const isInstalling = installingExtensions.some( (e) => e.extensionId === item.name ) @@ -51,32 +44,6 @@ const TensorRtExtensionItem: React.FC = ({ item }) => { ?.percentage ?? -1 : -1 - useEffect(() => { - const getSystemInfos = async () => { - const info = await systemInformation() - if (!info) { - setIsGpuSupported(false) - return - } - - const gpuSettings: GpuSetting | undefined = info.gpuSetting - if (!gpuSettings || gpuSettings.gpus.length === 0) { - setIsGpuSupported(false) - return - } - - const arch = gpuSettings.gpus[0].arch - if (!arch) { - setIsGpuSupported(false) - return - } - - const supportedGpuArch = ['ampere', 'ada'] - setIsGpuSupported(supportedGpuArch.includes(arch)) - } - getSystemInfos() - }, []) - useEffect(() => { const getExtensionInstallationState = async () => { const extension = extensionManager.getByName(item.name ?? '') @@ -116,16 +83,10 @@ const TensorRtExtensionItem: React.FC = ({ item }) => { const description = marked.parse(item.description ?? '', { async: false }) return ( -
+
-
- TensorRT-LLM Extension -
-

- v{item.version} -

- Experimental +
Additional Dependencies
{ // eslint-disable-next-line @typescript-eslint/naming-convention @@ -133,55 +94,16 @@ const TensorRtExtensionItem: React.FC = ({ item }) => { }
- {(!compatibility || compatibility['platform']?.includes(PLATFORM)) && - isGpuSupported ? ( + {(!compatibility || compatibility['platform']?.includes(PLATFORM)) && (
setPromptUpdateModal(true)} onCancelClick={onCancelInstallingClick} />
- ) : ( -
-
- Incompatible{' '} - - - - - - - {compatibility && - !compatibility['platform']?.includes(PLATFORM) ? ( - - Only available on{' '} - {compatibility?.platform - ?.map((e: string) => - e === 'win32' - ? 'Windows' - : e === 'linux' - ? 'Linux' - : 'MacOS' - ) - .join(', ')} - - ) : ( - - Your GPUs are not compatible with this extension - - )} - - - - -
-
- )} - {promptUpdateModal && ( - )}
) @@ -189,17 +111,17 @@ const TensorRtExtensionItem: React.FC = ({ item }) => { type InstallStateProps = { installProgress: number + compatibility?: Compatibility installState: InstallationState onInstallClick: () => void - onUpdateClick: () => void onCancelClick: () => void } const InstallStateIndicator: React.FC = ({ installProgress, + compatibility, installState, onInstallClick, - onUpdateClick, onCancelClick, }) => { if (installProgress !== -1) { @@ -226,11 +148,42 @@ const InstallStateIndicator: React.FC = ({ Installed
) - case 'Updatable': + case 'NotCompatible': return ( - +
+
+ Incompatible{' '} + + + + + + + {compatibility && + !compatibility['platform']?.includes(PLATFORM) ? ( + + Only available on{' '} + {compatibility?.platform + ?.map((e: string) => + e === 'win32' + ? 'Windows' + : e === 'linux' + ? 'Linux' + : 'MacOS' + ) + .join(', ')} + + ) : ( + + Your GPUs are not compatible with this extension + + )} + + + + +
+
) case 'NotInstalled': return ( @@ -253,4 +206,4 @@ const marked: Marked = new Marked({ }, }) -export default TensorRtExtensionItem +export default ExtensionItem diff --git a/web/screens/Settings/CoreExtensions/UpdateExtensionModal.tsx b/web/screens/Settings/CoreExtensions/UpdateExtensionModal.tsx deleted file mode 100644 index 62d0cf658d..0000000000 --- a/web/screens/Settings/CoreExtensions/UpdateExtensionModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from 'react' - -import { - Button, - Modal, - ModalClose, - ModalContent, - ModalFooter, - ModalHeader, - ModalPortal, - ModalTitle, - ModalTrigger, -} from '@janhq/uikit' -import { Paintbrush } from 'lucide-react' - -type Props = { - onUpdateClick: () => void -} - -const UpdateExtensionModal: React.FC = ({ onUpdateClick }) => { - return ( - - e.stopPropagation()}> -
- - - Update extension - -
-
- - - - Clean Thread - -

- Updating this extension may result in the loss of any custom models or - data associated with the current version. We recommend backing up any - important data before proceeding with the update. -

- -
- e.stopPropagation()}> - - - - - -
-
-
-
- ) -} - -export default React.memo(UpdateExtensionModal) diff --git a/web/screens/Settings/CoreExtensions/index.tsx b/web/screens/Settings/CoreExtensions/index.tsx index f5b66abeb9..f0d0892a6a 100644 --- a/web/screens/Settings/CoreExtensions/index.tsx +++ b/web/screens/Settings/CoreExtensions/index.tsx @@ -8,7 +8,7 @@ import Loader from '@/containers/Loader' import { formatExtensionsName } from '@/utils/converter' -import TensorRtExtensionItem from './TensorRtExtensionItem' +import ExtensionItem from './ExtensionItem' import { extensionManager } from '@/extension' import Extension from '@/extension/Extension' @@ -78,11 +78,6 @@ const ExtensionCatalog = () => {
{activeExtensions.map((item, i) => { - // TODO: this is bad code, rewrite it - if (item.name === '@janhq/tensorrt-llm-extension') { - return - } - return (
{ const selectedExtensionName = useAtomValue(selectedSettingAtom) const [settings, setSettings] = useState([]) + const [installationState, setInstallationState] = + useState('NotRequired') + const [baseExtension, setBaseExtension] = useState( + undefined + ) useEffect(() => { const getExtensionSettings = async () => { @@ -19,11 +29,15 @@ const ExtensionSetting: React.FC = () => { const allSettings: SettingComponentProps[] = [] const baseExtension = extensionManager.getByName(selectedExtensionName) if (!baseExtension) return + + setBaseExtension(baseExtension) if (typeof baseExtension.getSettings === 'function') { const setting = await baseExtension.getSettings() if (setting) allSettings.push(...setting) } setSettings(allSettings) + + setInstallationState(await baseExtension.installationState()) } getExtensionSettings() }, [selectedExtensionName]) @@ -48,13 +62,18 @@ const ExtensionSetting: React.FC = () => { setSettings(newSettings) } - if (settings.length === 0) return null - return ( - + <> + {settings.length > 0 && ( + + )} + {baseExtension && installationState !== 'NotRequired' && ( + + )} + ) } diff --git a/web/screens/Settings/SettingMenu/index.tsx b/web/screens/Settings/SettingMenu/index.tsx index 330e4c3c8d..36f686498f 100644 --- a/web/screens/Settings/SettingMenu/index.tsx +++ b/web/screens/Settings/SettingMenu/index.tsx @@ -20,7 +20,10 @@ const SettingMenu: React.FC = () => { for (const extension of extensions) { if (typeof extension.getSettings === 'function') { const settings = await extension.getSettings() - if (settings && settings.length > 0) { + if ( + (settings && settings.length > 0) || + (await extension.installationState()) !== 'NotRequired' + ) { extensionsMenu.push(extension.name ?? extension.url) } }