From a6a0cb325b51cc7ae880c2f7ad7770e8df522036 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 30 Dec 2024 18:27:51 +0800 Subject: [PATCH] feat: local engine management (#4334) * feat: local engine management * chore: move remote engine into engine page instead extension page * chore: set default engine from extension * chore: update endpoint update engine * chore: update event onEngineUpdate * chore: filter out engine download * chore: update version env * chore: select default engine variant base on user device specs * chore: symlink engine variants * chore: rolldown.config in mjs format * chore: binary codesign * fix: download state in footer bar and variant status * chore: update yarn.lock * fix: rimraf failure * fix: setup-node@v3 for built-in cache * fix: cov pipeline * fix: build syntax * chore: fix build step * fix: create engines folder on launch * chore: update ui delete engine variant with modal confirmation * chore: fix linter * chore: add installing progress for Local Engine download * chore: wording --------- Co-authored-by: Louis --- .../jan-electron-linter-and-test.yml | 10 +- core/src/browser/extension.ts | 1 + .../browser/extensions/enginesManagement.ts | 91 +++++ core/src/browser/extensions/index.ts | 5 + core/src/types/engine/index.ts | 28 ++ core/src/types/index.ts | 1 + .../jest.config.js | 5 + .../engine-management-extension/package.json | 47 +++ .../rolldown.config.mjs | 45 +++ .../src/@types/global.d.ts | 16 + .../engine-management-extension/src/error.ts | 10 + .../engine-management-extension/src/index.ts | 219 +++++++++++ .../src/node/cpuInfo.ts | 0 .../src/node/index.test.ts} | 181 ++------- .../src/node/index.ts} | 86 +++-- .../engine-management-extension/tsconfig.json | 15 + .../inference-cortex-extension/download.sh | 2 - .../inference-cortex-extension/package.json | 4 +- .../src/@types/global.d.ts | 10 + .../inference-cortex-extension/src/index.ts | 70 ++-- .../src/node/index.test.ts | 11 - .../src/node/index.ts | 31 +- extensions/model-extension/package.json | 2 +- extensions/yarn.lock | 296 +++++++++++++-- web/containers/Providers/EventListener.tsx | 6 + web/helpers/atoms/Extension.atom.ts | 12 +- web/hooks/useDownloadState.ts | 5 - web/hooks/useEngineManagement.ts | 328 ++++++++++++++++ web/package.json | 1 + web/screens/Settings/CoreExtensions/index.tsx | 90 +---- .../Settings/Engines/DeleteEngineVariant.tsx | 77 ++++ web/screens/Settings/Engines/Settings.tsx | 349 ++++++++++++++++++ web/screens/Settings/Engines/index.tsx | 261 +++++++++++++ web/screens/Settings/SettingDetail/index.tsx | 9 + .../SettingLeftPanel/SettingItem/index.tsx | 2 +- .../Settings/SettingLeftPanel/index.tsx | 38 +- web/screens/Settings/index.tsx | 1 + yarn.lock | 15 +- 38 files changed, 1962 insertions(+), 418 deletions(-) create mode 100644 core/src/browser/extensions/enginesManagement.ts create mode 100644 core/src/types/engine/index.ts create mode 100644 extensions/engine-management-extension/jest.config.js create mode 100644 extensions/engine-management-extension/package.json create mode 100644 extensions/engine-management-extension/rolldown.config.mjs create mode 100644 extensions/engine-management-extension/src/@types/global.d.ts create mode 100644 extensions/engine-management-extension/src/error.ts create mode 100644 extensions/engine-management-extension/src/index.ts rename extensions/{inference-cortex-extension => engine-management-extension}/src/node/cpuInfo.ts (100%) rename extensions/{inference-cortex-extension/src/node/execute.test.ts => engine-management-extension/src/node/index.test.ts} (63%) rename extensions/{inference-cortex-extension/src/node/execute.ts => engine-management-extension/src/node/index.ts} (68%) create mode 100644 extensions/engine-management-extension/tsconfig.json create mode 100644 web/hooks/useEngineManagement.ts create mode 100644 web/screens/Settings/Engines/DeleteEngineVariant.tsx create mode 100644 web/screens/Settings/Engines/Settings.tsx create mode 100644 web/screens/Settings/Engines/index.tsx diff --git a/.github/workflows/jan-electron-linter-and-test.yml b/.github/workflows/jan-electron-linter-and-test.yml index 3f0e1a1625..03d3a25c6a 100644 --- a/.github/workflows/jan-electron-linter-and-test.yml +++ b/.github/workflows/jan-electron-linter-and-test.yml @@ -44,16 +44,16 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ github.base_ref }} - - name: Use Node.js v20.9.0 + - name: Use Node.js 20.x uses: actions/setup-node@v3 with: - node-version: v20.9.0 + node-version: 20 - name: Install dependencies run: | - yarn + make build-joi yarn build:core - yarn build:joi + yarn - name: Run test coverage run: yarn test:coverage @@ -187,7 +187,7 @@ jobs: fetch-depth: 0 - name: Installing node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: node-version: 20 diff --git a/core/src/browser/extension.ts b/core/src/browser/extension.ts index b7a9fca4e1..1d641980b6 100644 --- a/core/src/browser/extension.ts +++ b/core/src/browser/extension.ts @@ -11,6 +11,7 @@ export enum ExtensionTypeEnum { Model = 'model', SystemMonitoring = 'systemMonitoring', HuggingFace = 'huggingFace', + Engine = 'engine', } export interface ExtensionType { diff --git a/core/src/browser/extensions/enginesManagement.ts b/core/src/browser/extensions/enginesManagement.ts new file mode 100644 index 0000000000..524546b053 --- /dev/null +++ b/core/src/browser/extensions/enginesManagement.ts @@ -0,0 +1,91 @@ +import { + InferenceEngine, + Engines, + EngineVariant, + EngineReleased, + DefaultEngineVariant, +} from '../../types' +import { BaseExtension, ExtensionTypeEnum } from '../extension' + +/** + * Engine management extension. Persists and retrieves engine management. + * @abstract + * @extends BaseExtension + */ +export abstract class EngineManagementExtension extends BaseExtension { + type(): ExtensionTypeEnum | undefined { + return ExtensionTypeEnum.Engine + } + + /** + * @returns A Promise that resolves to an object of list engines. + */ + abstract getEngines(): Promise + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to an array of installed engine. + */ + abstract getInstalledEngines(name: InferenceEngine): Promise + + /** + * @param name - Inference engine name. + * @param version - Version of the engine. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine by version. + */ + abstract getReleasedEnginesByVersion( + name: InferenceEngine, + version: string, + platform?: string + ): Promise + + /** + * @param name - Inference engine name. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine. + */ + abstract getLatestReleasedEngine( + name: InferenceEngine, + platform?: string + ): Promise + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to intall of engine. + */ + abstract installEngine( + name: InferenceEngine, + engineConfig: { variant: string; version?: string } + ): Promise<{ messages: string }> + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to unintall of engine. + */ + abstract uninstallEngine( + name: InferenceEngine, + engineConfig: { variant: string; version: string } + ): Promise<{ messages: string }> + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to an object of default engine. + */ + abstract getDefaultEngineVariant(name: InferenceEngine): Promise + + /** + * @body variant - string + * @body version - string + * @returns A Promise that resolves to set default engine. + */ + abstract setDefaultEngineVariant( + name: InferenceEngine, + engineConfig: { variant: string; version: string } + ): Promise<{ messages: string }> + + /** + * @returns A Promise that resolves to update engine. + */ + abstract updateEngine(name: InferenceEngine): Promise<{ messages: string }> +} diff --git a/core/src/browser/extensions/index.ts b/core/src/browser/extensions/index.ts index 85d5a85835..9dbfe1afe2 100644 --- a/core/src/browser/extensions/index.ts +++ b/core/src/browser/extensions/index.ts @@ -28,3 +28,8 @@ export { ModelExtension } from './model' * Base AI Engines. */ export * from './engines' + +/** + * Engines Management + */ +export * from './enginesManagement' diff --git a/core/src/types/engine/index.ts b/core/src/types/engine/index.ts new file mode 100644 index 0000000000..9e6f5c9c83 --- /dev/null +++ b/core/src/types/engine/index.ts @@ -0,0 +1,28 @@ +import { InferenceEngine } from '../../types' + +export type Engines = { + [key in InferenceEngine]: EngineVariant[] +} + +export type EngineVariant = { + engine: InferenceEngine + name: string + version: string +} + +export type DefaultEngineVariant = { + engine: InferenceEngine + variant: string + version: string +} + +export type EngineReleased = { + created_at: string + download_count: number + name: string + size: number +} + +export enum EngineEvent { + OnEngineUpdate = 'OnEngineUpdate', +} diff --git a/core/src/types/index.ts b/core/src/types/index.ts index 6627ebff9b..e30dd18c38 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -10,3 +10,4 @@ export * from './huggingface' export * from './miscellaneous' export * from './api' export * from './setting' +export * from './engine' diff --git a/extensions/engine-management-extension/jest.config.js b/extensions/engine-management-extension/jest.config.js new file mode 100644 index 0000000000..8bb37208d7 --- /dev/null +++ b/extensions/engine-management-extension/jest.config.js @@ -0,0 +1,5 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', +} diff --git a/extensions/engine-management-extension/package.json b/extensions/engine-management-extension/package.json new file mode 100644 index 0000000000..529105ea66 --- /dev/null +++ b/extensions/engine-management-extension/package.json @@ -0,0 +1,47 @@ +{ + "name": "@janhq/engine-management-extension", + "productName": "Engine Management", + "version": "1.0.0", + "description": "Extension for managing engines and their configurations", + "main": "dist/index.js", + "node": "dist/node/index.cjs.js", + "author": "Jan ", + "license": "MIT", + "scripts": { + "test": "jest", + "build": "rolldown -c rolldown.config.mjs", + "build:publish": "rimraf *.tgz --glob || true && yarn build && ../../.github/scripts/auto-sign.sh && npm pack && cpx *.tgz ../../pre-install" + }, + "exports": { + ".": "./dist/index.js", + "./main": "./dist/module.js" + }, + "devDependencies": { + "@rollup/plugin-replace": "^6.0.2", + "cpx": "^1.5.0", + "rimraf": "^3.0.2", + "rolldown": "^1.0.0-beta.1", + "ts-loader": "^9.5.0", + "typescript": "^5.3.3", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "@janhq/core": "../../core/package.tgz", + "cpu-instructions": "^0.0.13", + "ky": "^1.7.2", + "p-queue": "^8.0.1" + }, + "bundledDependencies": [ + "cpu-instructions", + "@janhq/core" + ], + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "dist/*", + "package.json", + "README.md" + ] +} diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs new file mode 100644 index 0000000000..edebf5efaf --- /dev/null +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -0,0 +1,45 @@ +import { defineConfig } from 'rolldown' +import replace from '@rollup/plugin-replace' +import pkgJson from './package.json' with { type: 'json' } + +export default defineConfig([ + { + input: 'src/index.ts', + output: { + format: 'esm', + file: 'dist/index.js', + }, + plugins: [ + replace({ + NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), + API_URL: JSON.stringify('http://127.0.0.1:39291'), + SOCKET_URL: JSON.stringify('ws://127.0.0.1:39291'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.42'), + }), + ], + }, + { + input: 'src/node/index.ts', + external: ['@janhq/core/node'], + output: { + format: 'cjs', + file: 'dist/node/index.cjs.js', + }, + plugins: [ + replace({ + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.42'), + }), + ], + }, + { + input: 'src/node/cpuInfo.ts', + output: { + format: 'cjs', + file: 'dist/node/cpuInfo.js', + }, + external: ['cpu-instructions'], + resolve: { + extensions: ['.ts', '.js', '.svg'], + }, + }, +]) diff --git a/extensions/engine-management-extension/src/@types/global.d.ts b/extensions/engine-management-extension/src/@types/global.d.ts new file mode 100644 index 0000000000..a8a5ee4515 --- /dev/null +++ b/extensions/engine-management-extension/src/@types/global.d.ts @@ -0,0 +1,16 @@ +export {} +declare global { + declare const API_URL: string + declare const CORTEX_ENGINE_VERSION: string + declare const SOCKET_URL: string + declare const NODE: string + + interface Core { + api: APIFunctions + events: EventEmitter + } + interface Window { + core?: Core | undefined + electronAPI?: any | undefined + } +} diff --git a/extensions/engine-management-extension/src/error.ts b/extensions/engine-management-extension/src/error.ts new file mode 100644 index 0000000000..50c75f22fd --- /dev/null +++ b/extensions/engine-management-extension/src/error.ts @@ -0,0 +1,10 @@ +/** + * Custom Engine Error + */ +export class EngineError extends Error { + message: string + constructor(message: string) { + super() + this.message = message + } +} diff --git a/extensions/engine-management-extension/src/index.ts b/extensions/engine-management-extension/src/index.ts new file mode 100644 index 0000000000..334df8cc1e --- /dev/null +++ b/extensions/engine-management-extension/src/index.ts @@ -0,0 +1,219 @@ +import { + EngineManagementExtension, + InferenceEngine, + DefaultEngineVariant, + Engines, + EngineVariant, + EngineReleased, + executeOnMain, + systemInformation, +} from '@janhq/core' +import ky, { HTTPError } from 'ky' +import PQueue from 'p-queue' +import { EngineError } from './error' + +/** + * JSONEngineManagementExtension is a EngineManagementExtension implementation that provides + * functionality for managing engines. + */ +export default class JSONEngineManagementExtension extends EngineManagementExtension { + queue = new PQueue({ concurrency: 1 }) + + /** + * Called when the extension is loaded. + */ + async onLoad() { + // Symlink Engines Directory + await executeOnMain(NODE, 'symlinkEngines') + // Run Healthcheck + this.queue.add(() => this.healthz()) + try { + const variant = await this.getDefaultEngineVariant( + InferenceEngine.cortex_llamacpp + ) + // Check whether should use bundled version or installed version + // Only use larger version + if (this.compareVersions(CORTEX_ENGINE_VERSION, variant.version) > 0) { + throw new EngineError( + 'Default engine version is smaller than bundled version' + ) + } + } catch (error) { + if ( + (error instanceof HTTPError && error.response.status === 400) || + error instanceof EngineError + ) { + const systemInfo = await systemInformation() + const variant = await executeOnMain( + NODE, + 'engineVariant', + systemInfo.gpuSetting + ) + await this.setDefaultEngineVariant(InferenceEngine.cortex_llamacpp, { + variant: variant, + version: `${CORTEX_ENGINE_VERSION}`, + }) + } else { + console.error('An unexpected error occurred:', error) + } + } + } + + /** + * Called when the extension is unloaded. + */ + onUnload() {} + + /** + * @returns A Promise that resolves to an object of list engines. + */ + async getEngines(): Promise { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/engines`) + .json() + .then((e) => e) + ) as Promise + } + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to an array of installed engine. + */ + async getInstalledEngines(name: InferenceEngine): Promise { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/engines/${name}`) + .json() + .then((e) => e) + ) as Promise + } + + /** + * @param name - Inference engine name. + * @param version - Version of the engine. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine by version. + */ + async getReleasedEnginesByVersion( + name: InferenceEngine, + version: string, + platform?: string + ) { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/engines/${name}/releases/${version}`) + .json() + .then((e) => + platform ? e.filter((r) => r.name.includes(platform)) : e + ) + ) as Promise + } + + /** + * @param name - Inference engine name. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine by version. + */ + async getLatestReleasedEngine(name: InferenceEngine, platform?: string) { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/engines/${name}/releases/latest`) + .json() + .then((e) => + platform ? e.filter((r) => r.name.includes(platform)) : e + ) + ) as Promise + } + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to intall of engine. + */ + async installEngine( + name: InferenceEngine, + engineConfig: { variant: string; version?: string } + ) { + return this.queue.add(() => + ky + .post(`${API_URL}/v1/engines/${name}/install`, { json: engineConfig }) + .then((e) => e) + ) as Promise<{ messages: string }> + } + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to unintall of engine. + */ + async uninstallEngine( + name: InferenceEngine, + engineConfig: { variant: string; version: string } + ) { + return this.queue.add(() => + ky + .delete(`${API_URL}/v1/engines/${name}/install`, { json: engineConfig }) + .then((e) => e) + ) as Promise<{ messages: string }> + } + + /** + * @param name - Inference engine name. + * @returns A Promise that resolves to an object of default engine. + */ + async getDefaultEngineVariant(name: InferenceEngine) { + return this.queue.add(() => + ky + .get(`${API_URL}/v1/engines/${name}/default`) + .json<{ messages: string }>() + .then((e) => e) + ) as Promise + } + + /** + * @body variant - string + * @body version - string + * @returns A Promise that resolves to set default engine. + */ + async setDefaultEngineVariant( + name: InferenceEngine, + engineConfig: { variant: string; version: string } + ) { + return this.queue.add(() => + ky + .post(`${API_URL}/v1/engines/${name}/default`, { json: engineConfig }) + .then((e) => e) + ) as Promise<{ messages: string }> + } + + /** + * @returns A Promise that resolves to update engine. + */ + async updateEngine(name: InferenceEngine) { + return this.queue.add(() => + ky.post(`${API_URL}/v1/engines/${name}/update`).then((e) => e) + ) as Promise<{ messages: string }> + } + + /** + * Do health check on cortex.cpp + * @returns + */ + async healthz(): Promise { + return ky + .get(`${API_URL}/healthz`, { + retry: { limit: 20, delay: () => 500, methods: ['get'] }, + }) + .then(() => {}) + } + + private compareVersions(version1: string, version2: string): number { + const parseVersion = (version: string) => version.split('.').map(Number) + + const [major1, minor1, patch1] = parseVersion(version1.replace(/^v/, '')) + const [major2, minor2, patch2] = parseVersion(version2.replace(/^v/, '')) + + if (major1 !== major2) return major1 - major2 + if (minor1 !== minor2) return minor1 - minor2 + return patch1 - patch2 + } +} diff --git a/extensions/inference-cortex-extension/src/node/cpuInfo.ts b/extensions/engine-management-extension/src/node/cpuInfo.ts similarity index 100% rename from extensions/inference-cortex-extension/src/node/cpuInfo.ts rename to extensions/engine-management-extension/src/node/cpuInfo.ts diff --git a/extensions/inference-cortex-extension/src/node/execute.test.ts b/extensions/engine-management-extension/src/node/index.test.ts similarity index 63% rename from extensions/inference-cortex-extension/src/node/execute.test.ts rename to extensions/engine-management-extension/src/node/index.test.ts index 1bcefce9d5..c73feb9c66 100644 --- a/extensions/inference-cortex-extension/src/node/execute.test.ts +++ b/extensions/engine-management-extension/src/node/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from '@jest/globals' -import { engineVariant, executableCortexFile } from './execute' +import engine from './index' import { GpuSetting } from '@janhq/core/node' import { cpuInfo } from 'cpu-instructions' import { fork } from 'child_process' @@ -62,20 +62,9 @@ describe('test executable cortex file', () => { Object.defineProperty(process, 'arch', { value: 'arm64', }) - expect(executableCortexFile(testSettings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: - originalPlatform === 'darwin' - ? expect.stringContaining(`cortex-server`) - : expect.anything(), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) mockFork.mockReturnValue(mockProcess) - expect(engineVariant(testSettings)).resolves.toEqual('mac-arm64') + expect(engine.engineVariant(testSettings)).resolves.toEqual('mac-arm64') }) it('executes on MacOS', () => { @@ -99,18 +88,7 @@ describe('test executable cortex file', () => { value: 'x64', }) - expect(executableCortexFile(testSettings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: - originalPlatform === 'darwin' - ? expect.stringContaining(`cortex-server`) - : expect.anything(), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) - expect(engineVariant(testSettings)).resolves.toEqual('mac-amd64') + expect(engine.engineVariant(testSettings)).resolves.toEqual('mac-amd64') }) it('executes on Windows CPU', () => { @@ -131,15 +109,7 @@ describe('test executable cortex file', () => { } mockFork.mockReturnValue(mockProcess) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server.exe`), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) - expect(engineVariant()).resolves.toEqual('windows-amd64-avx') + expect(engine.engineVariant()).resolves.toEqual('windows-amd64-avx') }) it('executes on Windows Cuda 11', () => { @@ -176,15 +146,8 @@ describe('test executable cortex file', () => { send: jest.fn(), } mockFork.mockReturnValue(mockProcess) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server.exe`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + + expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-avx2-cuda-11-7' ) }) @@ -221,15 +184,8 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server.exe`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + + expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-noavx-cuda-12-0' ) mockFork.mockReturnValue({ @@ -240,7 +196,7 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(engineVariant(settings)).resolves.toEqual( + expect(engine.engineVariant(settings)).resolves.toEqual( 'windows-amd64-avx2-cuda-12-0' ) }) @@ -261,15 +217,8 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) - expect(engineVariant()).resolves.toEqual('linux-amd64-noavx') + + expect(engine.engineVariant()).resolves.toEqual('linux-amd64-noavx') }) it('executes on Linux Cuda 11', () => { @@ -306,15 +255,9 @@ describe('test executable cortex file', () => { send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) + expect(engine.engineVariant(settings)).resolves.toBe( + 'linux-amd64-avx2-cuda-11-7' ) - expect(engineVariant(settings)).resolves.toBe('linux-amd64-avx2-cuda-11-7') }) it('executes on Linux Cuda 12', () => { @@ -349,15 +292,8 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + + expect(engine.engineVariant(settings)).resolves.toEqual( 'linux-amd64-avx2-cuda-12-0' ) }) @@ -383,16 +319,7 @@ describe('test executable cortex file', () => { send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + expect(engine.engineVariant(settings)).resolves.toEqual( `linux-amd64-${instruction}` ) }) @@ -416,15 +343,7 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server.exe`), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + expect(engine.engineVariant(settings)).resolves.toEqual( `windows-amd64-${instruction}` ) }) @@ -465,15 +384,7 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server.exe`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + expect(engine.engineVariant(settings)).resolves.toEqual( `windows-amd64-${instruction === 'avx512' || instruction === 'avx2' ? 'avx2' : 'noavx'}-cuda-12-0` ) }) @@ -514,15 +425,7 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual( + expect(engine.engineVariant(settings)).resolves.toEqual( `linux-amd64-${instruction === 'avx512' || instruction === 'avx2' ? 'avx2' : 'noavx'}-cuda-12-0` ) }) @@ -564,50 +467,8 @@ describe('test executable cortex file', () => { }), send: jest.fn(), }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: expect.stringContaining(`cortex-server`), - cudaVisibleDevices: '0', - vkVisibleDevices: '0', - }) - ) - expect(engineVariant(settings)).resolves.toEqual(`linux-amd64-vulkan`) - }) - }) - - // Generate test for different cpu instructions on MacOS - it(`executes on MacOS with different instructions`, () => { - Object.defineProperty(process, 'platform', { - value: 'darwin', - }) - const cpuInstructions = ['avx512', 'avx2', 'avx', 'noavx'] - cpuInstructions.forEach(() => { - Object.defineProperty(process, 'platform', { - value: 'darwin', - }) - const settings: GpuSetting = { - ...testSettings, - run_mode: 'cpu', - } - mockFork.mockReturnValue({ - on: jest.fn((event, callback) => { - if (event === 'message') { - callback('noavx') - } - }), - send: jest.fn(), - }) - expect(executableCortexFile(settings)).toEqual( - expect.objectContaining({ - enginePath: expect.stringContaining('shared'), - executablePath: - originalPlatform === 'darwin' - ? expect.stringContaining(`cortex-server`) - : expect.anything(), - cudaVisibleDevices: '', - vkVisibleDevices: '', - }) + expect(engine.engineVariant(settings)).resolves.toEqual( + `linux-amd64-vulkan` ) }) }) diff --git a/extensions/inference-cortex-extension/src/node/execute.ts b/extensions/engine-management-extension/src/node/index.ts similarity index 68% rename from extensions/inference-cortex-extension/src/node/execute.ts rename to extensions/engine-management-extension/src/node/index.ts index 0b091d464d..cf26d9290f 100644 --- a/extensions/inference-cortex-extension/src/node/execute.ts +++ b/extensions/engine-management-extension/src/node/index.ts @@ -1,13 +1,13 @@ import * as path from 'path' -import { GpuSetting, appResourcePath, log } from '@janhq/core/node' +import { + appResourcePath, + getJanDataFolderPath, + GpuSetting, + log, +} from '@janhq/core/node' import { fork } from 'child_process' +import { mkdir, readdir, symlink } from 'fs/promises' -export interface CortexExecutableOptions { - enginePath: string - executablePath: string - cudaVisibleDevices: string - vkVisibleDevices: string -} /** * The GPU runMode that will be set - either 'vulkan', 'cuda', or empty for cpu. * @param settings @@ -37,14 +37,6 @@ const os = (): string => { : 'linux-amd64' } -/** - * The cortex.cpp extension based on the current platform. - * @returns .exe if on Windows, otherwise an empty string. - */ -const extension = (): '.exe' | '' => { - return process.platform === 'win32' ? '.exe' : '' -} - /** * The CUDA version that will be set - either '11-7' or '12-0'. * @param settings @@ -89,30 +81,10 @@ const cpuInstructions = async (): Promise => { }) } -/** - * The executable options for the cortex.cpp extension. - */ -export const executableCortexFile = ( - gpuSetting?: GpuSetting -): CortexExecutableOptions => { - let cudaVisibleDevices = gpuSetting?.gpus_in_use.join(',') ?? '' - let vkVisibleDevices = gpuSetting?.gpus_in_use.join(',') ?? '' - let binaryName = `cortex-server${extension()}` - const binPath = path.join(__dirname, '..', 'bin') - return { - enginePath: path.join(appResourcePath(), 'shared'), - executablePath: path.join(binPath, binaryName), - cudaVisibleDevices, - vkVisibleDevices, - } -} - /** * Find which variant to run based on the current platform. */ -export const engineVariant = async ( - gpuSetting?: GpuSetting -): Promise => { +const engineVariant = async (gpuSetting?: GpuSetting): Promise => { const cpuInstruction = await cpuInstructions() log(`[CORTEX]: CPU instruction: ${cpuInstruction}`) let engineVariant = [ @@ -135,3 +107,45 @@ export const engineVariant = async ( log(`[CORTEX]: Engine variant: ${engineVariant}`) return engineVariant } + +/** + * Create symlink to each variant for the default bundled version + */ +const symlinkEngines = async () => { + const sourceEnginePath = path.join( + appResourcePath(), + 'shared', + 'engines', + 'cortex.llamacpp' + ) + const symlinkEnginePath = path.join( + getJanDataFolderPath(), + 'engines', + 'cortex.llamacpp' + ) + const variantFolders = await readdir(sourceEnginePath) + for (const variant of variantFolders) { + const targetVariantPath = path.join( + sourceEnginePath, + variant, + CORTEX_ENGINE_VERSION + ) + const symlinkVariantPath = path.join( + symlinkEnginePath, + variant, + CORTEX_ENGINE_VERSION + ) + + await mkdir(path.join(symlinkEnginePath, variant), { + recursive: true, + }).catch(console.error) + + await symlink(targetVariantPath, symlinkVariantPath).catch(console.error) + console.log(`Symlink created: ${targetVariantPath} -> ${symlinkEnginePath}`) + } +} + +export default { + engineVariant, + symlinkEngines, +} diff --git a/extensions/engine-management-extension/tsconfig.json b/extensions/engine-management-extension/tsconfig.json new file mode 100644 index 0000000000..891d28a605 --- /dev/null +++ b/extensions/engine-management-extension/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "ES6", + "moduleResolution": "node", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "skipLibCheck": true, + "rootDir": "./src" + }, + "include": ["./src"], + "exclude": ["src/**/*.test.ts", "rolldown.config.mjs"] +} diff --git a/extensions/inference-cortex-extension/download.sh b/extensions/inference-cortex-extension/download.sh index b0f3b36e35..0ccbdcb1ad 100755 --- a/extensions/inference-cortex-extension/download.sh +++ b/extensions/inference-cortex-extension/download.sh @@ -30,8 +30,6 @@ if [ "$OS_TYPE" == "Linux" ]; then download "${ENGINE_DOWNLOAD_URL}-linux-amd64-vulkan.tar.gz" -e --strip 1 -o "${SHARED_PATH}/engines/cortex.llamacpp/linux-amd64-vulkan/v${ENGINE_VERSION}" 1 download "${CUDA_DOWNLOAD_URL}/cuda-12-0-linux-amd64.tar.gz" -e --strip 1 -o "${SHARED_PATH}" 1 download "${CUDA_DOWNLOAD_URL}/cuda-11-7-linux-amd64.tar.gz" -e --strip 1 -o "${SHARED_PATH}" 1 - mkdir -p "${SHARED_PATH}/engines/cortex.llamacpp/deps" - touch "${SHARED_PATH}/engines/cortex.llamacpp/deps/keep" elif [ "$OS_TYPE" == "Darwin" ]; then # macOS downloads diff --git a/extensions/inference-cortex-extension/package.json b/extensions/inference-cortex-extension/package.json index b6c6563114..4429759468 100644 --- a/extensions/inference-cortex-extension/package.json +++ b/extensions/inference-cortex-extension/package.json @@ -47,7 +47,6 @@ }, "dependencies": { "@janhq/core": "../../core/package.tgz", - "cpu-instructions": "^0.0.13", "decompress": "^4.2.1", "fetch-retry": "^5.0.6", "ky": "^1.7.2", @@ -69,8 +68,7 @@ "tcp-port-used", "fetch-retry", "@janhq/core", - "decompress", - "cpu-instructions" + "decompress" ], "installConfig": { "hoistingLimits": "workspaces" diff --git a/extensions/inference-cortex-extension/src/@types/global.d.ts b/extensions/inference-cortex-extension/src/@types/global.d.ts index 139d836a56..3c310477d7 100644 --- a/extensions/inference-cortex-extension/src/@types/global.d.ts +++ b/extensions/inference-cortex-extension/src/@types/global.d.ts @@ -13,3 +13,13 @@ interface ModelOperationResponse { error?: any modelFile?: string } + +/** + * Cortex Executable Options Interface + */ +interface CortexExecutableOptions { + enginePath: string + executablePath: string + cudaVisibleDevices: string + vkVisibleDevices: string +} diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index 4cc322436d..537b3bc622 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -9,6 +9,7 @@ import { Model, executeOnMain, + EngineEvent, systemInformation, joinPath, LocalOAIEngine, @@ -18,9 +19,7 @@ import { fs, events, ModelEvent, - SystemInformation, dirName, - AppConfigurationEventName, } from '@janhq/core' import PQueue from 'p-queue' import ky from 'ky' @@ -112,21 +111,11 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { const systemInfo = await systemInformation() this.queue.add(() => executeOnMain(NODE, 'run', systemInfo)) this.queue.add(() => this.healthz()) - this.queue.add(() => this.setDefaultEngine(systemInfo)) this.subscribeToEvents() window.addEventListener('beforeunload', () => { this.clean() }) - - const currentMode = systemInfo.gpuSetting?.run_mode - - events.on(AppConfigurationEventName.OnConfigurationUpdate, async () => { - const systemInfo = await systemInformation() - // Update run mode on settings update - if (systemInfo.gpuSetting?.run_mode !== currentMode) - this.queue.add(() => this.setDefaultEngine(systemInfo)) - }) } async onUnload() { @@ -236,7 +225,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { * Do health check on cortex.cpp * @returns */ - private healthz(): Promise { + private async healthz(): Promise { return ky .get(`${CORTEX_API_URL}/healthz`, { retry: { @@ -248,36 +237,11 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { .then(() => {}) } - /** - * Set default engine variant on launch - */ - private async setDefaultEngine(systemInfo: SystemInformation) { - const variant = await executeOnMain( - NODE, - 'engineVariant', - systemInfo.gpuSetting - ) - return ( - ky - // Fallback support for legacy API - .post( - `${CORTEX_API_URL}/v1/engines/${InferenceEngine.cortex_llamacpp}/default?version=${CORTEX_ENGINE_VERSION}&variant=${variant}`, - { - json: { - version: CORTEX_ENGINE_VERSION, - variant, - }, - } - ) - .then(() => {}) - ) - } - /** * Clean cortex processes * @returns */ - private clean(): Promise { + private async clean(): Promise { return ky .delete(`${CORTEX_API_URL}/processmanager/destroy`, { timeout: 2000, // maximum 2 seconds @@ -301,6 +265,7 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { this.socket.addEventListener('message', (event) => { const data = JSON.parse(event.data) + const transferred = data.task.items.reduce( (acc: number, cur: any) => acc + cur.downloadedBytes, 0 @@ -320,17 +285,26 @@ export default class JanInferenceCortexExtension extends LocalOAIEngine { transferred: transferred, total: total, }, + downloadType: data.task.type, } ) - // Update models list from Hub - if (data.type === DownloadTypes.DownloadSuccess) { - // Delay for the state update from cortex.cpp - // Just to be sure - setTimeout(() => { - events.emit(ModelEvent.OnModelsUpdate, { - fetch: true, - }) - }, 500) + + if (data.task.type === 'Engine') { + events.emit(EngineEvent.OnEngineUpdate, { + type: DownloadTypes[data.type as keyof typeof DownloadTypes], + percent: percent, + id: data.task.id, + }) + } else { + if (data.type === DownloadTypes.DownloadSuccess) { + // Delay for the state update from cortex.cpp + // Just to be sure + setTimeout(() => { + events.emit(ModelEvent.OnModelsUpdate, { + fetch: true, + }) + }, 500) + } } }) diff --git a/extensions/inference-cortex-extension/src/node/index.test.ts b/extensions/inference-cortex-extension/src/node/index.test.ts index ff9d7c2fc3..bdfd99d035 100644 --- a/extensions/inference-cortex-extension/src/node/index.test.ts +++ b/extensions/inference-cortex-extension/src/node/index.test.ts @@ -54,17 +54,6 @@ jest.mock('child_process', () => ({ }, })) -jest.mock('./execute', () => ({ - executableCortexFile: () => { - return { - enginePath: 'enginePath', - executablePath: 'executablePath', - cudaVisibleDevices: 'cudaVisibleDevices', - vkVisibleDevices: 'vkVisibleDevices', - } - }, -})) - import index from './index' describe('dispose', () => { diff --git a/extensions/inference-cortex-extension/src/node/index.ts b/extensions/inference-cortex-extension/src/node/index.ts index a13bf60284..4ce35c83da 100644 --- a/extensions/inference-cortex-extension/src/node/index.ts +++ b/extensions/inference-cortex-extension/src/node/index.ts @@ -1,6 +1,5 @@ import path from 'path' import { getJanDataFolderPath, log, SystemInformation } from '@janhq/core/node' -import { engineVariant, executableCortexFile } from './execute' import { ProcessWatchdog } from './watchdog' // The HOST address to use for the Nitro subprocess @@ -15,21 +14,12 @@ function run(systemInfo?: SystemInformation): Promise { log(`[CORTEX]:: Spawning cortex subprocess...`) return new Promise(async (resolve, reject) => { - let executableOptions = executableCortexFile( - // If ngl is not set or equal to 0, run on CPU with correct instructions - systemInfo?.gpuSetting - ? { - ...systemInfo.gpuSetting, - run_mode: systemInfo.gpuSetting.run_mode, - } - : undefined - ) - + let gpuVisibleDevices = systemInfo?.gpuSetting?.gpus_in_use.join(',') ?? '' + let binaryName = `cortex-server${process.platform === 'win32' ? '.exe' : ''}` + const binPath = path.join(__dirname, '..', 'bin') + const executablePath = path.join(binPath, binaryName) // Execute the binary - log(`[CORTEX]:: Spawn cortex at path: ${executableOptions.executablePath}`) - log(`[CORTEX]:: Cortex engine path: ${executableOptions.enginePath}`) - - addEnvPaths(executableOptions.enginePath) + log(`[CORTEX]:: Spawn cortex at path: ${executablePath}`) const dataFolderPath = getJanDataFolderPath() if (watchdog) { @@ -37,7 +27,7 @@ function run(systemInfo?: SystemInformation): Promise { } watchdog = new ProcessWatchdog( - executableOptions.executablePath, + executablePath, [ '--start-server', '--port', @@ -48,14 +38,12 @@ function run(systemInfo?: SystemInformation): Promise { dataFolderPath, ], { - cwd: executableOptions.enginePath, env: { ...process.env, - ENGINE_PATH: executableOptions.enginePath, - CUDA_VISIBLE_DEVICES: executableOptions.cudaVisibleDevices, + CUDA_VISIBLE_DEVICES: gpuVisibleDevices, // Vulkan - Support 1 device at a time for now - ...(executableOptions.vkVisibleDevices?.length > 0 && { - GGML_VULKAN_DEVICE: executableOptions.vkVisibleDevices[0], + ...(gpuVisibleDevices?.length > 0 && { + GGML_VK_VISIBLE_DEVICES: gpuVisibleDevices, }), }, } @@ -96,5 +84,4 @@ export interface CortexProcessInfo { export default { run, dispose, - engineVariant, } diff --git a/extensions/model-extension/package.json b/extensions/model-extension/package.json index 90cd7f199e..4934422d81 100644 --- a/extensions/model-extension/package.json +++ b/extensions/model-extension/package.json @@ -2,7 +2,7 @@ "name": "@janhq/model-extension", "productName": "Model Management", "version": "1.0.35", - "description": "Model Management Extension provides model exploration and seamless downloads", + "description": "This extension manages model lists, model details, and model configurations", "main": "dist/index.js", "author": "Jan ", "license": "AGPL-3.0", diff --git a/extensions/yarn.lock b/extensions/yarn.lock index a3f8cb3d04..77a29c3fd7 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -518,6 +518,34 @@ __metadata: languageName: node linkType: hard +"@emnapi/core@npm:^1.3.1": + version: 1.3.1 + resolution: "@emnapi/core@npm:1.3.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.0.1" + tslib: "npm:^2.4.0" + checksum: 10c0/d3be1044ad704e2c486641bc18908523490f28c7d38bd12d9c1d4ce37d39dae6c4aecd2f2eaf44c6e3bd90eaf04e0591acc440b1b038cdf43cce078a355a0ea0 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.3.1": + version: 1.3.1 + resolution: "@emnapi/runtime@npm:1.3.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/060ffede50f1b619c15083312b80a9e62a5b0c87aa8c1b54854c49766c9d69f8d1d3d87bd963a647071263a320db41b25eaa50b74d6a80dcc763c23dbeaafd6c + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.0.1": + version: 1.0.1 + resolution: "@emnapi/wasi-threads@npm:1.0.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/1e0c8036b8d53e9b07cc9acf021705ef6c86ab6b13e1acda7fffaf541a2d3565072afb92597419173ced9ea14f6bf32fce149106e669b5902b825e8b499e5c6c + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -606,154 +634,183 @@ __metadata: "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 + languageName: node + linkType: hard + +"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": + version: 0.1.10 + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" + dependencies: + rxjs: "npm:^7.8.1" + ulidx: "npm:^2.3.0" + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-anthropic-extension%40workspace%3Ainference-anthropic-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-anthropic-extension%40workspace%3Ainference-anthropic-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-anthropic-extension%40workspace%3Ainference-anthropic-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cohere-extension%40workspace%3Ainference-cohere-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-cohere-extension%40workspace%3Ainference-cohere-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-cohere-extension%40workspace%3Ainference-cohere-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-groq-extension%40workspace%3Ainference-groq-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-groq-extension%40workspace%3Ainference-groq-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-groq-extension%40workspace%3Ainference-groq-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-martian-extension%40workspace%3Ainference-martian-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-martian-extension%40workspace%3Ainference-martian-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-martian-extension%40workspace%3Ainference-martian-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-mistral-extension%40workspace%3Ainference-mistral-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-mistral-extension%40workspace%3Ainference-mistral-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-mistral-extension%40workspace%3Ainference-mistral-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-nvidia-extension%40workspace%3Ainference-nvidia-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-nvidia-extension%40workspace%3Ainference-nvidia-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-nvidia-extension%40workspace%3Ainference-nvidia-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-openai-extension%40workspace%3Ainference-openai-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-openai-extension%40workspace%3Ainference-openai-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-openai-extension%40workspace%3Ainference-openai-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-openrouter-extension%40workspace%3Ainference-openrouter-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-openrouter-extension%40workspace%3Ainference-openrouter-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-openrouter-extension%40workspace%3Ainference-openrouter-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-triton-trt-llm-extension%40workspace%3Ainference-triton-trtllm-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Finference-triton-trt-llm-extension%40workspace%3Ainference-triton-trtllm-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Finference-triton-trt-llm-extension%40workspace%3Ainference-triton-trtllm-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Fmonitoring-extension%40workspace%3Amonitoring-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Ftensorrt-llm-extension%40workspace%3Atensorrt-llm-extension": version: 0.1.10 - resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=14cf2e&locator=%40janhq%2Ftensorrt-llm-extension%40workspace%3Atensorrt-llm-extension" + resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=048f32&locator=%40janhq%2Ftensorrt-llm-extension%40workspace%3Atensorrt-llm-extension" dependencies: rxjs: "npm:^7.8.1" ulidx: "npm:^2.3.0" - checksum: 10c0/d928ad13f353990eefcfbaa13539e49416d396c1bc3a5826f052942b04c266eb2d1335e072302b11e3d17cc1098b8026aee650105a4c9412f271383fc389a3a1 + checksum: 10c0/b48796ca697fffa5aeb33b5c20927a2c3c0b6080a17fccdfa4030919baa8d5c9e3d68d45f87da1eda7842285d7b642361f3793af8aac8d4399c5b13d940a6a42 languageName: node linkType: hard +"@janhq/engine-management-extension@workspace:engine-management-extension": + version: 0.0.0-use.local + resolution: "@janhq/engine-management-extension@workspace:engine-management-extension" + dependencies: + "@janhq/core": ../../core/package.tgz + "@rollup/plugin-replace": "npm:^6.0.2" + cpu-instructions: "npm:^0.0.13" + cpx: "npm:^1.5.0" + ky: "npm:^1.7.2" + p-queue: "npm:^8.0.1" + rimraf: "npm:^3.0.2" + rolldown: "npm:^1.0.0-beta.1" + ts-loader: "npm:^9.5.0" + typescript: "npm:^5.3.3" + webpack: "npm:^5.88.2" + webpack-cli: "npm:^5.1.4" + languageName: unknown + linkType: soft + "@janhq/inference-anthropic-extension@workspace:inference-anthropic-extension": version: 0.0.0-use.local resolution: "@janhq/inference-anthropic-extension@workspace:inference-anthropic-extension" @@ -802,7 +859,6 @@ __metadata: "@types/node": "npm:^20.11.4" "@types/os-utils": "npm:^0.0.4" "@types/tcp-port-used": "npm:^1.0.4" - cpu-instructions: "npm:^0.0.13" cpx: "npm:^1.5.0" decompress: "npm:^4.2.1" download-cli: "npm:^1.1.1" @@ -1907,6 +1963,17 @@ __metadata: languageName: node linkType: hard +"@napi-rs/wasm-runtime@npm:^0.2.4": + version: 0.2.6 + resolution: "@napi-rs/wasm-runtime@npm:0.2.6" + dependencies: + "@emnapi/core": "npm:^1.3.1" + "@emnapi/runtime": "npm:^1.3.1" + "@tybys/wasm-util": "npm:^0.9.0" + checksum: 10c0/f921676c1d5c75494bd704c6c0837fd05fe95f5d1cb7373e32987ef5e00c3a1e90b5052352bd4b60ee20c3fe592af2dbba3b0de0c637218c25590828dbc4067e + languageName: node + linkType: hard + "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -1936,6 +2003,92 @@ __metadata: languageName: node linkType: hard +"@rolldown/binding-darwin-arm64@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-darwin-arm64@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-darwin-x64@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-freebsd-x64@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-linux-x64-musl@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-wasm32-wasi@npm:1.0.0-beta.1-commit.f90856a" + dependencies: + "@napi-rs/wasm-runtime": "npm:^0.2.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-win32-ia32-msvc@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.1-commit.f90856a": + version: 1.0.0-beta.1-commit.f90856a + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.0.0-beta.1-commit.f90856a" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/plugin-commonjs@npm:^25.0.7": version: 25.0.8 resolution: "@rollup/plugin-commonjs@npm:25.0.8" @@ -2002,6 +2155,21 @@ __metadata: languageName: node linkType: hard +"@rollup/plugin-replace@npm:^6.0.2": + version: 6.0.2 + resolution: "@rollup/plugin-replace@npm:6.0.2" + dependencies: + "@rollup/pluginutils": "npm:^5.0.1" + magic-string: "npm:^0.30.3" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/71c0dea46f560c8dff59853446d43fa0e8258139a74d2af09fce5790d0540ff3d874c8fd9962cb049577d25327262bfc97485ef90b2a0a21bf28a9d3bd8c6d44 + languageName: node + linkType: hard + "@rollup/plugin-typescript@npm:^11.1.6": version: 11.1.6 resolution: "@rollup/plugin-typescript@npm:11.1.6" @@ -2085,6 +2253,15 @@ __metadata: languageName: node linkType: hard +"@tybys/wasm-util@npm:^0.9.0": + version: 0.9.0 + resolution: "@tybys/wasm-util@npm:0.9.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/f9fde5c554455019f33af6c8215f1a1435028803dc2a2825b077d812bed4209a1a64444a4ca0ce2ea7e1175c8d88e2f9173a36a33c199e8a5c671aa31de8242d + languageName: node + linkType: hard + "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -7837,6 +8014,59 @@ __metadata: languageName: node linkType: hard +"rolldown@npm:^1.0.0-beta.1": + version: 1.0.0-beta.1-commit.f90856a + resolution: "rolldown@npm:1.0.0-beta.1-commit.f90856a" + dependencies: + "@rolldown/binding-darwin-arm64": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-darwin-x64": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-freebsd-x64": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-linux-arm64-gnu": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-linux-arm64-musl": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-linux-x64-gnu": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-linux-x64-musl": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-wasm32-wasi": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-win32-arm64-msvc": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-win32-ia32-msvc": "npm:1.0.0-beta.1-commit.f90856a" + "@rolldown/binding-win32-x64-msvc": "npm:1.0.0-beta.1-commit.f90856a" + zod: "npm:^3.23.8" + peerDependencies: + "@babel/runtime": ">=7" + dependenciesMeta: + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-wasm32-wasi": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-ia32-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@babel/runtime": + optional: true + bin: + rolldown: bin/cli.js + checksum: 10c0/e9018052d305374b85b330357c65ad41ee94c3dd4f92827f03eaddc6998fb9fb6611fa21be248f1a18d233dfca9c6d268e5305403a1d0d61789ae646555bded6 + languageName: node + linkType: hard + "rollup-plugin-define@npm:^1.0.1": version: 1.0.1 resolution: "rollup-plugin-define@npm:1.0.1" @@ -8799,7 +9029,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.1.0, tslib@npm:^2.6.2": +"tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -9376,7 +9606,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.22.3, zod@npm:^3.22.4": +"zod@npm:^3.22.3, zod@npm:^3.22.4, zod@npm:^3.23.8": version: 3.24.1 resolution: "zod@npm:3.24.1" checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index bad1afda99..55c172beb8 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -108,6 +108,12 @@ const EventListener = () => { ...model.parameters, } as Partial) .catch((e) => console.debug(e)) + + toaster({ + title: 'Download Completed', + description: `Download ${state.modelId} completed`, + type: 'success', + }) } state.downloadState = 'end' setDownloadState(state) diff --git a/web/helpers/atoms/Extension.atom.ts b/web/helpers/atoms/Extension.atom.ts index 28b8a6bc13..257d429966 100644 --- a/web/helpers/atoms/Extension.atom.ts +++ b/web/helpers/atoms/Extension.atom.ts @@ -43,5 +43,15 @@ export const removeInstallingExtensionAtom = atom( const INACTIVE_ENGINE_PROVIDER = 'inActiveEngineProvider' export const inActiveEngineProviderAtom = atomWithStorage( INACTIVE_ENGINE_PROVIDER, - [] + [], + undefined, + { getOnInit: true } +) + +const SHOW_SETTING_ACTIVE_LOCAL_ENGINE = 'showSettingActiveLocalEngine' +export const showSettingActiveLocalEngineAtom = atomWithStorage( + SHOW_SETTING_ACTIVE_LOCAL_ENGINE, + [], + undefined, + { getOnInit: true } ) diff --git a/web/hooks/useDownloadState.ts b/web/hooks/useDownloadState.ts index 32a9d32556..632ca31a8f 100644 --- a/web/hooks/useDownloadState.ts +++ b/web/hooks/useDownloadState.ts @@ -54,11 +54,6 @@ export const setDownloadStateAtom = atom( (e) => e.id === state.modelId ) if (model) set(downloadedModelsAtom, (prev) => [...prev, model]) - toaster({ - title: 'Download Completed', - description: `Download ${state.modelId} completed`, - type: 'success', - }) } } else if (state.downloadState === 'error') { // download error diff --git a/web/hooks/useEngineManagement.ts b/web/hooks/useEngineManagement.ts new file mode 100644 index 0000000000..1272da81ad --- /dev/null +++ b/web/hooks/useEngineManagement.ts @@ -0,0 +1,328 @@ +import { useMemo } from 'react' + +import { + ExtensionTypeEnum, + EngineManagementExtension, + InferenceEngine, + EngineReleased, +} from '@janhq/core' +import { useAtom } from 'jotai' +import { atomWithStorage } from 'jotai/utils' +import useSWR from 'swr' + +import { extensionManager } from '@/extension/ExtensionManager' + +export const releasedEnginesCacheAtom = atomWithStorage<{ + data: EngineReleased[] + timestamp: number +} | null>('releasedEnginesCache', null, undefined, { getOnInit: true }) + +export const releasedEnginesLatestCacheAtom = atomWithStorage<{ + data: EngineReleased[] + timestamp: number +} | null>('releasedEnginesLatestCache', null, undefined, { getOnInit: true }) + +// fetcher function +async function fetchExtensionData( + extension: EngineManagementExtension | null, + method: (extension: EngineManagementExtension) => Promise +): Promise { + if (!extension) { + throw new Error('Extension not found') + } + return method(extension) +} + +/** + * @returns A Promise that resolves to an object of list engines. + */ +export function useGetEngines() { + const extension = useMemo( + () => + extensionManager.get( + ExtensionTypeEnum.Engine + ) ?? null, + [] + ) + + const { + data: engines, + error, + mutate, + } = useSWR( + extension ? 'engines' : null, + () => fetchExtensionData(extension, (ext) => ext.getEngines()), + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { engines, error, mutate } +} + +/** + * @param name - Inference engine name. + * @returns A Promise that resolves to an array of installed engine. + */ +export function useGetInstalledEngines(name: InferenceEngine) { + const extension = useMemo( + () => + extensionManager.get( + ExtensionTypeEnum.Engine + ) ?? null, + [] + ) + + const { + data: installedEngines, + error, + mutate, + } = useSWR( + extension ? 'installedEngines' : null, + () => fetchExtensionData(extension, (ext) => ext.getInstalledEngines(name)), + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { installedEngines, error, mutate } +} + +/** + * @param name - Inference engine name. + * @param version - Version of the engine. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine by version. + */ +export function useGetReleasedEnginesByVersion( + engine: InferenceEngine, + version: string | undefined, + platform: string +) { + const extension = useMemo( + () => + extensionManager.get( + ExtensionTypeEnum.Engine + ) ?? null, + [] + ) + + const [cache, setCache] = useAtom(releasedEnginesCacheAtom) + + const shouldFetch = Boolean(extension && version) + + const fetcher = async () => { + const now = Date.now() + const fifteenMinutes = 15 * 60 * 1000 + if (cache && cache.timestamp + fifteenMinutes > now) { + return cache.data // Use cached data + } + + const newData = await fetchExtensionData(extension, (ext) => + ext.getReleasedEnginesByVersion(engine, version!, platform) + ) + + setCache({ data: newData, timestamp: now }) + return newData + } + + const { data, error, mutate } = useSWR( + shouldFetch + ? `releasedEnginesByVersion-${engine}-${version}-${platform}` + : null, + fetcher, + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { + releasedEnginesByVersion: data, + error, + mutate, + } +} + +/** + * @param name - Inference engine name. + * @param platform - Optional to sort by operating system. macOS, linux, windows. + * @returns A Promise that resolves to an array of latest released engine. + */ + +export function useGetLatestReleasedEngine( + engine: InferenceEngine, + platform: string +) { + const extension = useMemo( + () => + extensionManager.get( + ExtensionTypeEnum.Engine + ) ?? null, + [] + ) + + const [cache, setCache] = useAtom(releasedEnginesLatestCacheAtom) + + const fetcher = async () => { + const now = Date.now() + const fifteenMinutes = 15 * 60 * 1000 + + if (cache && cache.timestamp + fifteenMinutes > now) { + return cache.data // Use cached data + } + + const newData = await fetchExtensionData(extension, (ext) => + ext.getLatestReleasedEngine(engine, platform) + ) + + setCache({ data: newData, timestamp: now }) + return newData + } + + const { data, error, mutate } = useSWR( + extension ? 'latestReleasedEngine' : null, + fetcher, + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { + latestReleasedEngine: data, + error, + mutate, + } +} + +/** + * @param name - Inference engine name. + * @returns A Promise that resolves to an object of default engine. + */ +export function useGetDefaultEngineVariant(name: InferenceEngine) { + const extension = useMemo( + () => + extensionManager.get(ExtensionTypeEnum.Engine), + [] + ) + + const { + data: defaultEngineVariant, + error, + mutate, + } = useSWR( + extension ? 'defaultEngineVariant' : null, + () => + fetchExtensionData(extension ?? null, (ext) => + ext.getDefaultEngineVariant(name) + ), + { + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + return { defaultEngineVariant, error, mutate } +} + +const getExtension = () => + extensionManager.get(ExtensionTypeEnum.Engine) ?? + null + +/** + * @body variant - string + * @body version - string + * @returns A Promise that resolves to set default engine. + */ +export const setDefaultEngineVariant = async ( + name: InferenceEngine, + engineConfig: { variant: string; version: string } +) => { + const extension = getExtension() + + if (!extension) { + throw new Error('Extension is not available') + } + + try { + // Call the extension's method + const response = await extension.setDefaultEngineVariant(name, engineConfig) + return response + } catch (error) { + console.error('Failed to set default engine variant:', error) + throw error + } +} + +/** + * @body variant - string + * @body version - string + * @returns A Promise that resolves to set default engine. + */ +export const updateEngine = async (name: InferenceEngine) => { + const extension = getExtension() + + if (!extension) { + throw new Error('Extension is not available') + } + + try { + // Call the extension's method + const response = await extension.updateEngine(name) + return response + } catch (error) { + console.error('Failed to set default engine variant:', error) + throw error + } +} + +/** + * @param name - Inference engine name. + * @returns A Promise that resolves to intall of engine. + */ +export const installEngine = async ( + name: InferenceEngine, + engineConfig: { variant: string; version?: string } +) => { + const extension = getExtension() + + if (!extension) { + throw new Error('Extension is not available') + } + + try { + // Call the extension's method + const response = await extension.installEngine(name, engineConfig) + return response + } catch (error) { + console.error('Failed to install engine variant:', error) + throw error + } +} + +/** + * @param name - Inference engine name. + * @returns A Promise that resolves to unintall of engine. + */ +export const uninstallEngine = async ( + name: InferenceEngine, + engineConfig: { variant: string; version: string } +) => { + const extension = getExtension() + + if (!extension) { + throw new Error('Extension is not available') + } + + try { + // Call the extension's method + const response = await extension.uninstallEngine(name, engineConfig) + return response + } catch (error) { + console.error('Failed to install engine variant:', error) + throw error + } +} diff --git a/web/package.json b/web/package.json index 8136825e7c..598c748d92 100644 --- a/web/package.json +++ b/web/package.json @@ -52,6 +52,7 @@ "slate-dom": "0.111.0", "slate-history": "0.110.3", "slate-react": "0.110.3", + "swr": "^2.2.5", "tailwind-merge": "^2.0.0", "tailwindcss": "3.3.5", "ulidx": "^2.3.0", diff --git a/web/screens/Settings/CoreExtensions/index.tsx b/web/screens/Settings/CoreExtensions/index.tsx index 65bc74ba8b..57dd49eda6 100644 --- a/web/screens/Settings/CoreExtensions/index.tsx +++ b/web/screens/Settings/CoreExtensions/index.tsx @@ -1,34 +1,23 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import React, { useState, useEffect, useRef, useCallback } from 'react' +import React, { useState, useEffect, useRef } from 'react' -import { InferenceEngine } from '@janhq/core' +import { Button, ScrollArea, Badge, Input } from '@janhq/joi' -import { Button, ScrollArea, Badge, Switch, Input } from '@janhq/joi' -import { useAtom } from 'jotai' import { SearchIcon } from 'lucide-react' import { Marked, Renderer } from 'marked' import Loader from '@/containers/Loader' -import SetupRemoteModel from '@/containers/SetupRemoteModel' - import { formatExtensionsName } from '@/utils/converter' import { extensionManager } from '@/extension' import Extension from '@/extension/Extension' -import { inActiveEngineProviderAtom } from '@/helpers/atoms/Extension.atom' - -type EngineExtension = { - provider: InferenceEngine -} & Extension const ExtensionCatalog = () => { const [coreActiveExtensions, setCoreActiveExtensions] = useState( [] ) - const [engineActiveExtensions, setEngineActiveExtensions] = useState< - EngineExtension[] - >([]) + const [searchText, setSearchText] = useState('') const [showLoading, setShowLoading] = useState(false) const fileInputRef = useRef(null) @@ -67,7 +56,6 @@ const ExtensionCatalog = () => { } setCoreActiveExtensions(extensionsMenu) - setEngineActiveExtensions(engineMenu as any) } getAllSettings() }, []) @@ -113,23 +101,6 @@ const ExtensionCatalog = () => { } } - const [inActiveEngineProvider, setInActiveEngineProvider] = useAtom( - inActiveEngineProviderAtom - ) - - const onSwitchChange = useCallback( - (name: string) => { - if (inActiveEngineProvider.includes(name)) { - setInActiveEngineProvider( - [...inActiveEngineProvider].filter((x) => x !== name) - ) - } else { - setInActiveEngineProvider([...inActiveEngineProvider, name]) - } - }, - [inActiveEngineProvider, setInActiveEngineProvider] - ) - return ( <> @@ -158,61 +129,6 @@ const ExtensionCatalog = () => {
- {engineActiveExtensions.length !== 0 && ( -
-
- Model Providers -
-
- )} - {engineActiveExtensions - .filter((x) => x.name.includes(searchText.toLowerCase().trim())) - .sort((a, b) => a.provider.localeCompare(b.provider)) - .map((item, i) => { - return ( -
-
-
-
-
- {item.productName?.replace('Inference Engine', '') ?? - formatExtensionsName(item.name)} -
- - v{item.version} - -

{item.provider}

-
-
- {!inActiveEngineProvider.includes(item.provider) && ( - - )} - onSwitchChange(item.provider)} - /> -
-
- { -
- } -
-
- ) - })} - {coreActiveExtensions.length > 0 && (
diff --git a/web/screens/Settings/Engines/DeleteEngineVariant.tsx b/web/screens/Settings/Engines/DeleteEngineVariant.tsx new file mode 100644 index 0000000000..12e30590c3 --- /dev/null +++ b/web/screens/Settings/Engines/DeleteEngineVariant.tsx @@ -0,0 +1,77 @@ +import { memo, useState } from 'react' + +import { EngineReleased, InferenceEngine } from '@janhq/core' +import { Button, Modal, ModalClose } from '@janhq/joi' + +import { Trash2Icon } from 'lucide-react' + +import { + uninstallEngine, + useGetDefaultEngineVariant, + useGetInstalledEngines, +} from '@/hooks/useEngineManagement' + +const DeleteEngineVariant = ({ + variant, + engine, +}: { + variant: EngineReleased + engine: InferenceEngine +}) => { + const [open, setOpen] = useState(false) + + const { mutate: mutateInstalledEngines } = useGetInstalledEngines(engine) + const { defaultEngineVariant, mutate: mutateDefaultEngineVariant } = + useGetDefaultEngineVariant(engine) + + return ( + Delete {variant.name}} + open={open} + onOpenChange={() => setOpen(!open)} + trigger={ + + } + content={ +
+

+ Are you sure you want to delete this variant? +

+
+ { + setOpen(!open) + e.stopPropagation() + }} + > + + + + + +
+
+ } + /> + ) +} + +export default memo(DeleteEngineVariant) diff --git a/web/screens/Settings/Engines/Settings.tsx b/web/screens/Settings/Engines/Settings.tsx new file mode 100644 index 0000000000..f3498670ae --- /dev/null +++ b/web/screens/Settings/Engines/Settings.tsx @@ -0,0 +1,349 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import React, { useCallback, useEffect, useMemo, useState } from 'react' + +import { + DownloadEvent, + EngineEvent, + events, + InferenceEngine, +} from '@janhq/core' +import { Button, ScrollArea, Badge, Select, Progress } from '@janhq/joi' +import { Trash2Icon } from 'lucide-react' +import { twMerge } from 'tailwind-merge' + +import { + useGetDefaultEngineVariant, + useGetInstalledEngines, + useGetLatestReleasedEngine, + setDefaultEngineVariant, + installEngine, + updateEngine, + uninstallEngine, + useGetReleasedEnginesByVersion, +} from '@/hooks/useEngineManagement' + +import { formatDownloadPercentage } from '@/utils/converter' +const os = () => { + switch (PLATFORM) { + case 'win32': + return 'windows' + case 'linux': + return 'linux' + + default: + return 'mac' + } +} + +const EngineSettings = ({ engine }: { engine: InferenceEngine }) => { + const { installedEngines, mutate: mutateInstalledEngines } = + useGetInstalledEngines(engine) + const { defaultEngineVariant, mutate: mutateDefaultEngineVariant } = + useGetDefaultEngineVariant(engine) + const { latestReleasedEngine } = useGetLatestReleasedEngine(engine, os()) + const { releasedEnginesByVersion } = useGetReleasedEnginesByVersion( + engine, + defaultEngineVariant?.version as string, + os() + ) + const [installingEngines, setInstallingEngines] = useState< + Map + >(new Map()) + + const isEngineUpdated = + latestReleasedEngine && + latestReleasedEngine.every((item) => + item.name.includes( + defaultEngineVariant?.version.replace(/^v/, '') as string + ) + ) + + const availableVariants = useMemo( + () => + latestReleasedEngine?.map((e) => + e.name.replace( + `${defaultEngineVariant?.version.replace(/^v/, '') as string}-`, + '' + ) + ), + [latestReleasedEngine, defaultEngineVariant] + ) + const options = + installedEngines && + installedEngines + .filter((x: any) => x.version === defaultEngineVariant?.version) + .map((x: any) => ({ + name: x.name, + value: x.name, + })) + + const installedEngineByVersion = installedEngines?.filter( + (x: any) => x.version === defaultEngineVariant?.version + ) + + const [selectedVariants, setSelectedVariants] = useState( + defaultEngineVariant?.variant + ) + + const selectedVariant = useMemo( + () => + options?.map((e) => e.value).includes(selectedVariants) + ? selectedVariants + : undefined, + [selectedVariants, options] + ) + + useEffect(() => { + if (defaultEngineVariant?.variant) { + setSelectedVariants(defaultEngineVariant.variant || '') + } + }, [defaultEngineVariant]) + + const handleEngineUpdate = useCallback( + (event: { id: string; type: DownloadEvent; percent: number }) => { + mutateInstalledEngines() + mutateDefaultEngineVariant() + // Backward compatible support - cortex.cpp returns full variant file name + const variant: string | undefined = event.id.includes('.tar.gz') + ? availableVariants?.find((e) => event.id.includes(`${e}.tar.gz`)) + : availableVariants?.find((e) => event.id.includes(e)) + if (!variant) return + setInstallingEngines((prev) => { + prev.set(variant, event.percent) + return prev + }) + if ( + event.type === DownloadEvent.onFileDownloadError || + event.type === DownloadEvent.onFileDownloadStopped || + event.type === DownloadEvent.onFileDownloadSuccess + ) { + setInstallingEngines((prev) => { + prev.delete(variant) + return prev + }) + } + }, + [ + mutateDefaultEngineVariant, + mutateInstalledEngines, + setInstallingEngines, + availableVariants, + ] + ) + + useEffect(() => { + events.on(EngineEvent.OnEngineUpdate, handleEngineUpdate) + return () => { + events.off(EngineEvent.OnEngineUpdate, handleEngineUpdate) + } + }, [handleEngineUpdate]) + + const handleChangeVariant = (e: string) => { + setSelectedVariants(e) + setDefaultEngineVariant(engine, { + variant: e, + version: String(defaultEngineVariant?.version), + }) + } + return ( + +
+
+
+
+
+
+
Engine Version
+
+
+ + {defaultEngineVariant?.version} + +
+
+
+
+
+
+ +
+
+
+
+
+
+
Check Updates
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ {engine} Backend +
+
+

+ Choose the default variant that best suited for your + hardware. See more information here. +

+
+
+
+
+ } + placeholder="Search" + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + clearable={searchText.length > 0} + onClear={() => setSearchText('')} + /> +
+ {/*
+ + +
*/} +
+ +
+
+
+ Local Engine +
+ {engines && + Object.entries(engines) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .filter(([_, value]) => !(value as { type?: string }).type) + .map(([key]) => { + return + })} +
+
+ + {engineActiveExtensions.length !== 0 && ( +
+
+
+ Remote Engine +
+ {engineActiveExtensions + .filter((x) => x.name.includes(searchText.toLowerCase().trim())) + .sort((a, b) => a.provider.localeCompare(b.provider)) + .map((item, i) => { + return ( +
+
+
+
+
+ {item.productName?.replace( + 'Inference Engine', + '' + ) ?? formatExtensionsName(item.name)} +
+ + v{item.version} + +

{item.provider}

+
+
+ onSwitchChange(item.provider)} + /> + {!inActiveEngineProvider.includes(item.provider) && ( + + )} +
+
+ { +
+ } +
+
+ ) + })} +
+
+ )} + + ) +} + +export default Engines diff --git a/web/screens/Settings/SettingDetail/index.tsx b/web/screens/Settings/SettingDetail/index.tsx index 993303955c..f183aaff53 100644 --- a/web/screens/Settings/SettingDetail/index.tsx +++ b/web/screens/Settings/SettingDetail/index.tsx @@ -1,8 +1,11 @@ +import { InferenceEngine } from '@janhq/core' import { useAtomValue } from 'jotai' import Advanced from '@/screens/Settings/Advanced' import AppearanceOptions from '@/screens/Settings/Appearance' import ExtensionCatalog from '@/screens/Settings/CoreExtensions' +import Engines from '@/screens/Settings/Engines' +import EngineSettings from '@/screens/Settings/Engines/Settings' import ExtensionSetting from '@/screens/Settings/ExtensionSetting' import Hotkeys from '@/screens/Settings/Hotkeys' import MyModels from '@/screens/Settings/MyModels' @@ -14,6 +17,9 @@ const SettingDetail = () => { const selectedSetting = useAtomValue(selectedSettingAtom) switch (selectedSetting) { + case 'Engines': + return + case 'Extensions': return @@ -32,6 +38,9 @@ const SettingDetail = () => { case 'My Models': return + case InferenceEngine.cortex_llamacpp: + return + default: return } diff --git a/web/screens/Settings/SettingLeftPanel/SettingItem/index.tsx b/web/screens/Settings/SettingLeftPanel/SettingItem/index.tsx index b8c1994c56..a5da186742 100644 --- a/web/screens/Settings/SettingLeftPanel/SettingItem/index.tsx +++ b/web/screens/Settings/SettingLeftPanel/SettingItem/index.tsx @@ -29,7 +29,7 @@ const SettingItem = ({ name, setting }: Props) => { > diff --git a/web/screens/Settings/SettingLeftPanel/index.tsx b/web/screens/Settings/SettingLeftPanel/index.tsx index 87ddde4d41..a8b2a148f8 100644 --- a/web/screens/Settings/SettingLeftPanel/index.tsx +++ b/web/screens/Settings/SettingLeftPanel/index.tsx @@ -1,18 +1,28 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { memo, useEffect, useState } from 'react' import { useAtomValue } from 'jotai' import LeftPanelContainer from '@/containers/LeftPanelContainer' +import { useGetEngines } from '@/hooks/useEngineManagement' + import SettingItem from './SettingItem' import { extensionManager } from '@/extension' -import { inActiveEngineProviderAtom } from '@/helpers/atoms/Extension.atom' +import { + inActiveEngineProviderAtom, + showSettingActiveLocalEngineAtom, +} from '@/helpers/atoms/Extension.atom' import { janSettingScreenAtom } from '@/helpers/atoms/Setting.atom' const SettingLeftPanel = () => { + const { engines } = useGetEngines() const settingScreens = useAtomValue(janSettingScreenAtom) const inActiveEngineProvider = useAtomValue(inActiveEngineProviderAtom) + const showSettingActiveLocalEngine = useAtomValue( + showSettingActiveLocalEngineAtom + ) const [extensionHasSettings, setExtensionHasSettings] = useState< { name?: string; setting: string }[] @@ -84,12 +94,36 @@ const SettingLeftPanel = () => { /> ))} + {engines && + Object.entries(engines) + .filter(([key]) => !showSettingActiveLocalEngine.includes(key)) + .filter(([_, value]) => !(value as { type?: string }).type).length > + 0 && ( + <> +
+ +
+ + {engines && + Object.entries(engines) + .filter(([_, value]) => !(value as { type?: string }).type) + .filter( + ([key]) => !showSettingActiveLocalEngine.includes(key) + ) + .map(([key]) => { + return + })} + + )} + {engineHasSettings.filter( (x) => !inActiveEngineProvider.includes(x.provider) ).length > 0 && (
)} diff --git a/web/screens/Settings/index.tsx b/web/screens/Settings/index.tsx index 5003babcd6..66e11d07ed 100644 --- a/web/screens/Settings/index.tsx +++ b/web/screens/Settings/index.tsx @@ -17,6 +17,7 @@ export const SettingScreenList = [ 'Keyboard Shortcuts', 'Privacy', 'Advanced Settings', + 'Engines', 'Extensions', ] as const diff --git a/yarn.lock b/yarn.lock index f3bd63fc4d..9664a2a9ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1798,6 +1798,7 @@ __metadata: slate-dom: "npm:0.111.0" slate-history: "npm:0.110.3" slate-react: "npm:0.110.3" + swr: "npm:^2.2.5" tailwind-merge: "npm:^2.0.0" tailwindcss: "npm:3.3.5" ts-jest: "npm:^29.2.5" @@ -18995,6 +18996,18 @@ __metadata: languageName: node linkType: hard +"swr@npm:^2.2.5": + version: 2.3.0 + resolution: "swr@npm:2.3.0" + dependencies: + dequal: "npm:^2.0.3" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/192497881013654bc82d2787b60ad0701113e8ae41c511dfa8d55bcf58582657a92a4cb2854d4ea2ceaa1055e67e58daf9bd98ada2786a3035ba12898da578f1 + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -19972,7 +19985,7 @@ __metadata: languageName: node linkType: hard -"use-sync-external-store@npm:^1.2.0": +"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:^1.4.0": version: 1.4.0 resolution: "use-sync-external-store@npm:1.4.0" peerDependencies: