diff --git a/extensions/engine-management-extension/rolldown.config.mjs b/extensions/engine-management-extension/rolldown.config.mjs index 7e8322698..8f15b148d 100644 --- a/extensions/engine-management-extension/rolldown.config.mjs +++ b/extensions/engine-management-extension/rolldown.config.mjs @@ -11,9 +11,11 @@ export default defineConfig([ }, define: { NODE: JSON.stringify(`${pkgJson.name}/${pkgJson.node}`), - API_URL: JSON.stringify(`http://127.0.0.1:${process.env.CORTEX_API_PORT ?? "39291"}`), + API_URL: JSON.stringify( + `http://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` + ), PLATFORM: JSON.stringify(process.platform), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.54'), DEFAULT_REMOTE_ENGINES: JSON.stringify(engines), DEFAULT_REMOTE_MODELS: JSON.stringify(models), DEFAULT_REQUEST_PAYLOAD_TRANSFORM: JSON.stringify( @@ -36,7 +38,7 @@ export default defineConfig([ file: 'dist/node/index.cjs.js', }, define: { - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.54'), }, }, ]) diff --git a/extensions/engine-management-extension/src/index.ts b/extensions/engine-management-extension/src/index.ts index 083aa8d50..3b11db020 100644 --- a/extensions/engine-management-extension/src/index.ts +++ b/extensions/engine-management-extension/src/index.ts @@ -286,7 +286,7 @@ export default class JanEngineManagementExtension extends EngineManagementExtens if ( !installedEngines.some( (e) => e.name === variant.variant && e.version === variant.version - ) + ) || variant.version < CORTEX_ENGINE_VERSION ) { throw new EngineError( 'Default engine is not available, use bundled version.' diff --git a/extensions/hardware-management-extension/src/index.ts b/extensions/hardware-management-extension/src/index.ts index c2edc6159..f64c2eea8 100644 --- a/extensions/hardware-management-extension/src/index.ts +++ b/extensions/hardware-management-extension/src/index.ts @@ -1,8 +1,4 @@ -import { - executeOnMain, - HardwareManagementExtension, - HardwareInformation, -} from '@janhq/core' +import { HardwareManagementExtension, HardwareInformation } from '@janhq/core' import ky from 'ky' import PQueue from 'p-queue' diff --git a/extensions/inference-cortex-extension/bin/version.txt b/extensions/inference-cortex-extension/bin/version.txt index 09a269b4f..9f3b89c18 100644 --- a/extensions/inference-cortex-extension/bin/version.txt +++ b/extensions/inference-cortex-extension/bin/version.txt @@ -1 +1 @@ -1.0.11-rc6 +1.0.11-rc7 diff --git a/extensions/inference-cortex-extension/download.bat b/extensions/inference-cortex-extension/download.bat index fd20e8c8d..0646cff07 100644 --- a/extensions/inference-cortex-extension/download.bat +++ b/extensions/inference-cortex-extension/download.bat @@ -2,7 +2,7 @@ set BIN_PATH=./bin set SHARED_PATH=./../../electron/shared set /p CORTEX_VERSION=<./bin/version.txt -set ENGINE_VERSION=0.1.49 +set ENGINE_VERSION=0.1.54 @REM Download cortex.llamacpp binaries set DOWNLOAD_URL=https://github.com/janhq/cortex.llamacpp/releases/download/v%ENGINE_VERSION%/cortex.llamacpp-%ENGINE_VERSION%-windows-amd64 diff --git a/extensions/inference-cortex-extension/download.sh b/extensions/inference-cortex-extension/download.sh index c32160184..f5fefb341 100755 --- a/extensions/inference-cortex-extension/download.sh +++ b/extensions/inference-cortex-extension/download.sh @@ -2,7 +2,7 @@ # Read CORTEX_VERSION CORTEX_VERSION=$(cat ./bin/version.txt) -ENGINE_VERSION=0.1.49 +ENGINE_VERSION=0.1.54 CORTEX_RELEASE_URL="https://github.com/janhq/cortex.cpp/releases/download" ENGINE_DOWNLOAD_URL="https://github.com/janhq/cortex.llamacpp/releases/download/v${ENGINE_VERSION}/cortex.llamacpp-${ENGINE_VERSION}" CUDA_DOWNLOAD_URL="https://github.com/janhq/cortex.llamacpp/releases/download/v${ENGINE_VERSION}" diff --git a/extensions/inference-cortex-extension/jest.config.js b/extensions/inference-cortex-extension/jest.config.js deleted file mode 100644 index b413e106d..000000000 --- a/extensions/inference-cortex-extension/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; \ No newline at end of file diff --git a/extensions/inference-cortex-extension/package.json b/extensions/inference-cortex-extension/package.json index a4558dc8f..00e7c346e 100644 --- a/extensions/inference-cortex-extension/package.json +++ b/extensions/inference-cortex-extension/package.json @@ -8,7 +8,7 @@ "author": "Jan ", "license": "AGPL-3.0", "scripts": { - "test": "jest", + "test": "vitest run", "build": "rolldown -c rolldown.config.mjs", "downloadcortex:linux:darwin": "./download.sh", "downloadcortex:win32": "download.bat", @@ -35,17 +35,15 @@ "rolldown": "1.0.0-beta.1", "run-script-os": "^1.1.6", "ts-jest": "^29.1.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vitest": "^3.0.8" }, "dependencies": { "@janhq/core": "../../core/package.tgz", - "decompress": "^4.2.1", "fetch-retry": "^5.0.6", "ky": "^1.7.2", "p-queue": "^8.0.1", "rxjs": "^7.8.1", - "tcp-port-used": "^1.0.2", - "terminate": "2.6.1", "ulidx": "^2.3.0" }, "engines": { diff --git a/extensions/inference-cortex-extension/rolldown.config.mjs b/extensions/inference-cortex-extension/rolldown.config.mjs index f8d0587b1..5b5fed296 100644 --- a/extensions/inference-cortex-extension/rolldown.config.mjs +++ b/extensions/inference-cortex-extension/rolldown.config.mjs @@ -13,9 +13,13 @@ export default defineConfig([ define: { NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`), SETTINGS: JSON.stringify(defaultSettingJson), - CORTEX_API_URL: JSON.stringify(`http://127.0.0.1:${process.env.CORTEX_API_PORT ?? "39291"}`), - CORTEX_SOCKET_URL: JSON.stringify(`ws://127.0.0.1:${process.env.CORTEX_API_PORT ?? "39291"}`), - CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.49'), + CORTEX_API_URL: JSON.stringify( + `http://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` + ), + CORTEX_SOCKET_URL: JSON.stringify( + `ws://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` + ), + CORTEX_ENGINE_VERSION: JSON.stringify('v0.1.54'), }, }, { @@ -31,7 +35,9 @@ export default defineConfig([ extensions: ['.js', '.ts', '.json'], }, define: { - CORTEX_API_URL: JSON.stringify(`http://127.0.0.1:${process.env.CORTEX_API_PORT ?? "39291"}`), + CORTEX_API_URL: JSON.stringify( + `http://127.0.0.1:${process.env.CORTEX_API_PORT ?? '39291'}` + ), }, platform: 'node', }, diff --git a/extensions/inference-cortex-extension/src/index.ts b/extensions/inference-cortex-extension/src/index.ts index a16718942..5e1adf322 100644 --- a/extensions/inference-cortex-extension/src/index.ts +++ b/extensions/inference-cortex-extension/src/index.ts @@ -10,15 +10,11 @@ import { Model, executeOnMain, EngineEvent, - joinPath, LocalOAIEngine, InferenceEngine, - getJanDataFolderPath, extractModelLoadParams, - fs, events, ModelEvent, - dirName, } from '@janhq/core' import PQueue from 'p-queue' import ky from 'ky' diff --git a/extensions/inference-cortex-extension/src/node/index.test.ts b/extensions/inference-cortex-extension/src/node/index.test.ts index bdfd99d03..6a1e168f3 100644 --- a/extensions/inference-cortex-extension/src/node/index.test.ts +++ b/extensions/inference-cortex-extension/src/node/index.test.ts @@ -1,6 +1,14 @@ -jest.mock('@janhq/core/node', () => ({ - ...jest.requireActual('@janhq/core/node'), +import { describe, it, expect, vi } from 'vitest' +// Mocks + +const CORTEX_API_URL = 'http://localhost:3000' +vi.stubGlobal('CORTEX_API_URL', CORTEX_API_URL) + +vi.mock('@janhq/core/node', (actual) => ({ + ...actual(), getJanDataFolderPath: () => '', + appResourcePath: () => '/mock/path', + log: vi.fn(), getSystemResourceInfo: () => { return { cpu: { @@ -30,25 +38,36 @@ jest.mock('@janhq/core/node', () => ({ }, })) -jest.mock('fs', () => ({ +vi.mock('fs', () => ({ default: { readdirSync: () => [], }, })) -jest.mock('child_process', () => ({ +vi.mock('./watchdog', () => { + return { + ProcessWatchdog: vi.fn().mockImplementation(() => { + return { + start: vi.fn(), + terminate: vi.fn(), + } + }), + } +}) + +vi.mock('child_process', () => ({ exec: () => { return { - stdout: { on: jest.fn() }, - stderr: { on: jest.fn() }, - on: jest.fn(), + stdout: { on: vi.fn() }, + stderr: { on: vi.fn() }, + on: vi.fn(), } }, spawn: () => { return { - stdout: { on: jest.fn() }, - stderr: { on: jest.fn() }, - on: jest.fn(), + stdout: { on: vi.fn() }, + stderr: { on: vi.fn() }, + on: vi.fn(), pid: '111', } }, @@ -56,28 +75,70 @@ jest.mock('child_process', () => ({ import index from './index' -describe('dispose', () => { - it('should dispose a model successfully on Mac', async () => { - Object.defineProperty(process, 'platform', { - value: 'darwin', +describe('Cortex extension node interface', () => { + describe('run', () => { + it('should start the cortex subprocess on macOS', async () => { + Object.defineProperty(process, 'platform', { + value: 'darwin', + }) + + const result = await index.run() + expect(result).toBeUndefined() }) - // Call the dispose function - const result = await index.dispose() + it('should start the cortex subprocess on Windows', async () => { + Object.defineProperty(process, 'platform', { + value: 'win32', + }) - // Assert that the result is as expected - expect(result).toBeUndefined() + const result = await index.run() + expect(result).toBeUndefined() + }) + + it('should set the proper environment variables based on platform', async () => { + // Test for Windows + Object.defineProperty(process, 'platform', { + value: 'win32', + }) + process.env.PATH = '/original/path' + + await index.run() + expect(process.env.PATH).toContain('/original/path') + + // Test for non-Windows (macOS/Linux) + Object.defineProperty(process, 'platform', { + value: 'darwin', + }) + process.env.LD_LIBRARY_PATH = '/original/ld/path' + + await index.run() + expect(process.env.LD_LIBRARY_PATH).toContain('/original/ld/path') + }) }) - it('should kill the subprocess successfully on Windows', async () => { - Object.defineProperty(process, 'platform', { - value: 'win32', + describe('dispose', () => { + it('should dispose a model successfully on Mac', async () => { + Object.defineProperty(process, 'platform', { + value: 'darwin', + }) + + // Call the dispose function + const result = index.dispose() + + // Assert that the result is as expected + expect(result).toBeUndefined() }) - // Call the killSubprocess function - const result = await index.dispose() + it('should kill the subprocess successfully on Windows', async () => { + Object.defineProperty(process, 'platform', { + value: 'win32', + }) - // Assert that the result is as expected - expect(result).toBeUndefined() + // Call the dispose function + const result = index.dispose() + + // Assert that the result is as expected + expect(result).toBeUndefined() + }) }) }) diff --git a/extensions/inference-cortex-extension/src/node/index.ts b/extensions/inference-cortex-extension/src/node/index.ts index ec0c87c99..87a1c7096 100644 --- a/extensions/inference-cortex-extension/src/node/index.ts +++ b/extensions/inference-cortex-extension/src/node/index.ts @@ -1,13 +1,7 @@ import path from 'path' -import { - appResourcePath, - getJanDataFolderPath, - log, -} from '@janhq/core/node' +import { appResourcePath, getJanDataFolderPath, log } from '@janhq/core/node' import { ProcessWatchdog } from './watchdog' -// The HOST address to use for the Nitro subprocess -const LOCAL_PORT = CORTEX_API_URL.split(":").pop() ?? "39291" let watchdog: ProcessWatchdog | undefined = undefined /** @@ -37,6 +31,9 @@ function run(): Promise { watchdog.terminate() } + // The HOST address to use for the cortex subprocess + const LOCAL_PORT = CORTEX_API_URL.split(':').pop() ?? '39291' + watchdog = new ProcessWatchdog( executablePath, [ diff --git a/web/screens/Settings/Engines/LocalEngineSettings.tsx b/web/screens/Settings/Engines/LocalEngineSettings.tsx index e8e375b90..d1b68a02b 100644 --- a/web/screens/Settings/Engines/LocalEngineSettings.tsx +++ b/web/screens/Settings/Engines/LocalEngineSettings.tsx @@ -67,13 +67,18 @@ const LocalEngineSettings = ({ engine }: { engine: InferenceEngine }) => { RecommendEngineVariantAtom ) - const isEngineUpdated = - latestReleasedEngine && - latestReleasedEngine.some((item) => - item.name.includes( - defaultEngineVariant?.version.replace(/^v/, '') as string - ) + const isEngineUpdated = useMemo(() => { + if (!latestReleasedEngine || !defaultEngineVariant) return false + const latestVariant = latestReleasedEngine.find((item) => + item.name.includes(defaultEngineVariant.variant) ) + if (!latestVariant) return false + const latestVersion = latestVariant.name + .replace(defaultEngineVariant.variant, '') + .replaceAll('-', '') + const currentVersion = defaultEngineVariant.version.replace(/^v/, '') + return latestVersion <= currentVersion + }, [latestReleasedEngine, defaultEngineVariant]) const availableVariants = useMemo( () => diff --git a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx index e582450a9..23b137be8 100644 --- a/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/ChatInput/index.tsx @@ -170,7 +170,7 @@ const ChatInput = () => { !!fileUpload || (activeAssistant?.tools && !activeAssistant?.tools[0]?.enabled && - !activeAssistant?.model.settings?.vision_model) + !activeAssistant?.model.settings?.mmproj) ) { e.stopPropagation() } else { @@ -193,7 +193,7 @@ const ChatInput = () => { {!!fileUpload || (activeAssistant?.tools && !activeAssistant?.tools[0]?.enabled && - !activeAssistant?.model.settings?.vision_model && ( + !activeAssistant?.model.settings?.mmproj && ( <> {!!fileUpload && ( @@ -231,13 +231,13 @@ const ChatInput = () => {
  • { - if (activeAssistant?.model.settings?.vision_model) { + if (activeAssistant?.model.settings?.mmproj) { imageInputRef.current?.click() setShowAttachmentMenus(false) } diff --git a/web/screens/Thread/ThreadCenterPanel/index.tsx b/web/screens/Thread/ThreadCenterPanel/index.tsx index d32fe1ade..02fd7c7fd 100644 --- a/web/screens/Thread/ThreadCenterPanel/index.tsx +++ b/web/screens/Thread/ThreadCenterPanel/index.tsx @@ -61,7 +61,7 @@ const ThreadCenterPanel = () => { const activeAssistant = useAtomValue(activeAssistantAtom) const chatWidth = useAtomValue(chatWidthAtom) const upload = uploader() - const acceptedFormat: Accept = activeAssistant?.model.settings?.vision_model + const acceptedFormat: Accept = activeAssistant?.model.settings?.mmproj ? { 'application/pdf': ['.pdf'], 'image/jpeg': ['.jpeg'], @@ -83,7 +83,7 @@ const ThreadCenterPanel = () => { if ( e.dataTransfer.items.length === 1 && ((activeAssistant?.tools && activeAssistant?.tools[0]?.enabled) || - activeAssistant?.model.settings?.vision_model) + activeAssistant?.model.settings?.mmproj) ) { setDragOver(true) } else if ( @@ -105,7 +105,7 @@ const ThreadCenterPanel = () => { rejectFiles.length !== 0 || (activeAssistant?.tools && !activeAssistant?.tools[0]?.enabled && - !activeAssistant?.model.settings?.vision_model) + !activeAssistant?.model.settings?.mmproj) ) return const imageType = files[0]?.type.includes('image') @@ -182,7 +182,7 @@ const ThreadCenterPanel = () => {
    {isDragReject ? `Currently, we only support 1 attachment at the same time with ${ - activeAssistant?.model.settings?.vision_model + activeAssistant?.model.settings?.mmproj ? 'PDF, JPEG, JPG, PNG' : 'PDF' } format` @@ -190,7 +190,7 @@ const ThreadCenterPanel = () => {
    {!isDragReject && (

    - {activeAssistant?.model.settings?.vision_model + {activeAssistant?.model.settings?.mmproj ? 'PDF, JPEG, JPG, PNG' : 'PDF'}