diff --git a/core/rolldown.config.mjs b/core/rolldown.config.mjs index d95f8de8e..ea488df33 100644 --- a/core/rolldown.config.mjs +++ b/core/rolldown.config.mjs @@ -25,7 +25,6 @@ export default defineConfig([ '@types/pacote', '@npmcli/arborist', 'ulidx', - 'node-fetch', 'fs', 'request', 'crypto', diff --git a/core/src/browser/core.test.ts b/core/src/browser/core.test.ts index 720ea9dcf..117298eb6 100644 --- a/core/src/browser/core.test.ts +++ b/core/src/browser/core.test.ts @@ -2,7 +2,6 @@ import { openExternalUrl } from './core' import { joinPath } from './core' import { openFileExplorer } from './core' import { getJanDataFolderPath } from './core' -import { abortDownload } from './core' import { executeOnMain } from './core' describe('test core apis', () => { @@ -53,18 +52,6 @@ describe('test core apis', () => { expect(result).toBe('/path/to/jan/data') }) - it('should abort download', async () => { - const fileName = 'testFile' - globalThis.core = { - api: { - abortDownload: jest.fn().mockResolvedValue('aborted'), - }, - } - const result = await abortDownload(fileName) - expect(globalThis.core.api.abortDownload).toHaveBeenCalledWith(fileName) - expect(result).toBe('aborted') - }) - it('should execute function on main process', async () => { const extension = 'testExtension' const method = 'testMethod' diff --git a/core/src/browser/core.ts b/core/src/browser/core.ts index a0abbb43e..43b5f9d48 100644 --- a/core/src/browser/core.ts +++ b/core/src/browser/core.ts @@ -1,9 +1,4 @@ -import { - DownloadRequest, - FileStat, - NetworkConfig, - SystemInformation, -} from '../types' +import { SystemInformation } from '../types' /** * Execute a extension module function in main process @@ -14,42 +9,19 @@ import { * @returns Promise * */ -const executeOnMain: ( - extension: string, - method: string, - ...args: any[] -) => Promise = (extension, method, ...args) => - globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args) +const executeOnMain: (extension: string, method: string, ...args: any[]) => Promise = ( + extension, + method, + ...args +) => globalThis.core?.api?.invokeExtensionFunc(extension, method, ...args) -/** - * Downloads a file from a URL and saves it to the local file system. - * - * @param {DownloadRequest} downloadRequest - The request to download the file. - * @param {NetworkConfig} network - Optional object to specify proxy/whether to ignore SSL certificates. - * - * @returns {Promise} A promise that resolves when the file is downloaded. - */ -const downloadFile: ( - downloadRequest: DownloadRequest, - network?: NetworkConfig -) => Promise = (downloadRequest, network) => - globalThis.core?.api?.downloadFile(downloadRequest, network) - -/** - * Aborts the download of a specific file. - * @param {string} fileName - The name of the file whose download is to be aborted. - * @returns {Promise} A promise that resolves when the download has been aborted. - */ -const abortDownload: (fileName: string) => Promise = (fileName) => - globalThis.core.api?.abortDownload(fileName) /** * Gets Jan's data folder path. * * @returns {Promise} A Promise that resolves with Jan's data folder path. */ -const getJanDataFolderPath = (): Promise => - globalThis.core.api?.getJanDataFolderPath() +const getJanDataFolderPath = (): Promise => globalThis.core.api?.getJanDataFolderPath() /** * Opens the file explorer at a specific path. @@ -72,16 +44,14 @@ const joinPath: (paths: string[]) => Promise = (paths) => * @param path - The file path to retrieve dirname. * @returns {Promise} A promise that resolves the dirname. */ -const dirName: (path: string) => Promise = (path) => - globalThis.core.api?.dirName(path) +const dirName: (path: string) => Promise = (path) => globalThis.core.api?.dirName(path) /** * Retrieve the basename from an url. * @param path - The path to retrieve. * @returns {Promise} A promise that resolves with the basename. */ -const baseName: (paths: string) => Promise = (path) => - globalThis.core.api?.baseName(path) +const baseName: (paths: string) => Promise = (path) => globalThis.core.api?.baseName(path) /** * Opens an external URL in the default web browser. @@ -97,15 +67,13 @@ const openExternalUrl: (url: string) => Promise = (url) => * * @returns {Promise} - A promise that resolves with the resource path. */ -const getResourcePath: () => Promise = () => - globalThis.core.api?.getResourcePath() +const getResourcePath: () => Promise = () => globalThis.core.api?.getResourcePath() /** * Gets the user's home path. * @returns return user's home path */ -const getUserHomePath = (): Promise => - globalThis.core.api?.getUserHomePath() +const getUserHomePath = (): Promise => globalThis.core.api?.getUserHomePath() /** * Log to file from browser processes. @@ -123,10 +91,8 @@ const log: (message: string, fileName?: string) => void = (message, fileName) => * * @returns {Promise} - A promise that resolves with a boolean indicating whether the path is a subdirectory. */ -const isSubdirectory: (from: string, to: string) => Promise = ( - from: string, - to: string -) => globalThis.core.api?.isSubdirectory(from, to) +const isSubdirectory: (from: string, to: string) => Promise = (from: string, to: string) => + globalThis.core.api?.isSubdirectory(from, to) /** * Get system information @@ -159,8 +125,6 @@ export type RegisterExtensionPoint = ( */ export { executeOnMain, - downloadFile, - abortDownload, getJanDataFolderPath, openFileExplorer, getResourcePath, diff --git a/core/src/browser/extension.test.ts b/core/src/browser/extension.test.ts index 2db14a24e..879258876 100644 --- a/core/src/browser/extension.test.ts +++ b/core/src/browser/extension.test.ts @@ -39,11 +39,6 @@ describe('BaseExtension', () => { expect(baseExtension.onUnload).toBeDefined() }) - it('should have installationState() return "NotRequired"', async () => { - const installationState = await baseExtension.installationState() - expect(installationState).toBe('NotRequired') - }) - it('should install the extension', async () => { await baseExtension.install() // Add your assertions here @@ -84,11 +79,6 @@ describe('BaseExtension', () => { expect(baseExtension.onUnload).toBeDefined() }) - it('should have installationState() return "NotRequired"', async () => { - const installationState = await baseExtension.installationState() - expect(installationState).toBe('NotRequired') - }) - it('should install the extension', async () => { await baseExtension.install() // Add your assertions here diff --git a/core/src/browser/extension.ts b/core/src/browser/extension.ts index f68f37fa0..a050b9d59 100644 --- a/core/src/browser/extension.ts +++ b/core/src/browser/extension.ts @@ -24,17 +24,6 @@ export interface Compatibility { version: string } -const ALL_INSTALLATION_STATE = [ - 'NotRequired', // not required. - 'Installed', // require and installed. Good to go. - 'NotInstalled', // require to be installed. - 'Corrupted', // require but corrupted. Need to redownload. - 'NotCompatible', // require but not compatible. -] as const - -export type InstallationStateTuple = typeof ALL_INSTALLATION_STATE -export type InstallationState = InstallationStateTuple[number] - /** * Represents a base extension. * This class should be extended by any class that represents an extension. @@ -175,15 +164,6 @@ export abstract class BaseExtension implements ExtensionType { return } - /** - * Determine if the prerequisites for the extension are installed. - * - * @returns {boolean} true if the prerequisites are installed, false otherwise. - */ - async installationState(): Promise { - return 'NotRequired' - } - /** * Install the prerequisites for the extension. * diff --git a/core/src/browser/fs.ts b/core/src/browser/fs.ts index 9240b3876..7aa5f4d92 100644 --- a/core/src/browser/fs.ts +++ b/core/src/browser/fs.ts @@ -55,17 +55,23 @@ const unlinkSync = (...args: any[]) => globalThis.core.api?.unlinkSync(...args) */ const appendFileSync = (...args: any[]) => globalThis.core.api?.appendFileSync(...args) +/** + * Copies a file from the source path to the destination path. + * @param src + * @param dest + * @returns + */ const copyFile: (src: string, dest: string) => Promise = (src, dest) => globalThis.core.api?.copyFile(src, dest) /** * Gets the list of gguf files in a directory - * + * * @param path - The paths to the file. * @returns {Promise<{any}>} - A promise that resolves with the list of gguf and non-gguf files */ -const getGgufFiles: (paths: string[]) => Promise = ( - paths) => globalThis.core.api?.getGgufFiles(paths) +const getGgufFiles: (paths: string[]) => Promise = (paths) => + globalThis.core.api?.getGgufFiles(paths) /** * Gets the file's stats. diff --git a/core/src/node/api/common/adapter.ts b/core/src/node/api/common/adapter.ts index 2beacf325..b0c8173a9 100644 --- a/core/src/node/api/common/adapter.ts +++ b/core/src/node/api/common/adapter.ts @@ -1,25 +1,21 @@ import { AppRoute, - DownloadRoute, ExtensionRoute, FileManagerRoute, FileSystemRoute, } from '../../../types/api' -import { Downloader } from '../processors/download' import { FileSystem } from '../processors/fs' import { Extension } from '../processors/extension' import { FSExt } from '../processors/fsExt' import { App } from '../processors/app' export class RequestAdapter { - downloader: Downloader fileSystem: FileSystem extension: Extension fsExt: FSExt app: App constructor(observer?: Function) { - this.downloader = new Downloader(observer) this.fileSystem = new FileSystem() this.extension = new Extension() this.fsExt = new FSExt() @@ -28,9 +24,7 @@ export class RequestAdapter { // TODO: Clearer Factory pattern here process(route: string, ...args: any) { - if (route in DownloadRoute) { - return this.downloader.process(route, ...args) - } else if (route in FileSystemRoute) { + if (route in FileSystemRoute) { return this.fileSystem.process(route, ...args) } else if (route in ExtensionRoute) { return this.extension.process(route, ...args) diff --git a/core/src/node/api/processors/download.test.ts b/core/src/node/api/processors/download.test.ts deleted file mode 100644 index c4b171a7d..000000000 --- a/core/src/node/api/processors/download.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { Downloader } from './download' -import { DownloadEvent } from '../../../types/api' -import { DownloadManager } from '../../helper/download' - -jest.mock('../../helper', () => ({ - getJanDataFolderPath: jest.fn().mockReturnValue('path/to/folder'), -})) - -jest.mock('../../helper/path', () => ({ - validatePath: jest.fn().mockReturnValue('path/to/folder'), - normalizeFilePath: () => - process.platform === 'win32' ? 'C:\\Users\\path\\to\\file.gguf' : '/Users/path/to/file.gguf', -})) - -jest.mock( - 'request', - jest.fn().mockReturnValue(() => ({ - on: jest.fn(), - })) -) - -jest.mock('fs', () => ({ - createWriteStream: jest.fn(), -})) - -const requestMock = jest.fn((options, callback) => { - callback(new Error('Test error'), null) -}) -jest.mock('request', () => requestMock) - -jest.mock('request-progress', () => { - return jest.fn().mockImplementation(() => { - return { - on: jest.fn().mockImplementation((event, callback) => { - if (event === 'error') { - callback(new Error('Download failed')) - } - return { - on: jest.fn().mockImplementation((event, callback) => { - if (event === 'error') { - callback(new Error('Download failed')) - } - return { - on: jest.fn().mockImplementation((event, callback) => { - if (event === 'error') { - callback(new Error('Download failed')) - } - return { pipe: jest.fn() } - }), - } - }), - } - }), - } - }) -}) - -describe('Downloader', () => { - beforeEach(() => { - jest.resetAllMocks() - }) - - it('should pause download correctly', () => { - const observer = jest.fn() - const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file' - - const downloader = new Downloader(observer) - const pauseMock = jest.fn() - DownloadManager.instance.networkRequests[fileName] = { pause: pauseMock } - - downloader.pauseDownload(observer, fileName) - - expect(pauseMock).toHaveBeenCalled() - }) - - it('should resume download correctly', () => { - const observer = jest.fn() - const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file' - - const downloader = new Downloader(observer) - const resumeMock = jest.fn() - DownloadManager.instance.networkRequests[fileName] = { resume: resumeMock } - - downloader.resumeDownload(observer, fileName) - - expect(resumeMock).toHaveBeenCalled() - }) - - it('should handle aborting a download correctly', () => { - const observer = jest.fn() - const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file' - - const downloader = new Downloader(observer) - const abortMock = jest.fn() - DownloadManager.instance.networkRequests[fileName] = { abort: abortMock } - - downloader.abortDownload(observer, fileName) - - expect(abortMock).toHaveBeenCalled() - expect(observer).toHaveBeenCalledWith( - DownloadEvent.onFileDownloadError, - expect.objectContaining({ - error: 'aborted', - }) - ) - }) - - it('should handle download fail correctly', () => { - const observer = jest.fn() - const fileName = process.platform === 'win32' ? 'C:\\path\\to\\file' : 'path/to/file.gguf' - - const downloader = new Downloader(observer) - - downloader.downloadFile(observer, { - localPath: fileName, - url: 'http://127.0.0.1', - }) - expect(observer).toHaveBeenCalledWith( - DownloadEvent.onFileDownloadError, - expect.objectContaining({ - error: expect.anything(), - }) - ) - }) -}) diff --git a/core/src/node/api/processors/download.ts b/core/src/node/api/processors/download.ts deleted file mode 100644 index 709ad9687..000000000 --- a/core/src/node/api/processors/download.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { resolve, sep } from 'path' -import { DownloadEvent } from '../../../types/api' -import { normalizeFilePath } from '../../helper/path' -import { getJanDataFolderPath } from '../../helper' -import { DownloadManager } from '../../helper/download' -import { createWriteStream, renameSync } from 'fs' -import { Processor } from './Processor' -import { DownloadRequest, DownloadState, NetworkConfig } from '../../../types' - -export class Downloader implements Processor { - observer?: Function - - constructor(observer?: Function) { - this.observer = observer - } - - process(key: string, ...args: any[]): any { - const instance = this as any - const func = instance[key] - return func(this.observer, ...args) - } - - downloadFile(observer: any, downloadRequest: DownloadRequest, network?: NetworkConfig) { - const request = require('request') - const progress = require('request-progress') - - const strictSSL = !network?.ignoreSSL - const proxy = network?.proxy?.startsWith('http') ? network.proxy : undefined - - const { localPath, url } = downloadRequest - let normalizedPath = localPath - if (typeof localPath === 'string') { - normalizedPath = normalizeFilePath(localPath) - } - const array = normalizedPath.split(sep) - const fileName = array.pop() ?? '' - const modelId = downloadRequest.modelId ?? array.pop() ?? '' - - const destination = resolve(getJanDataFolderPath(), normalizedPath) - const rq = request({ url, strictSSL, proxy }) - - // Put request to download manager instance - DownloadManager.instance.setRequest(normalizedPath, rq) - - // Downloading file to a temp file first - const downloadingTempFile = `${destination}.download` - - // adding initial download state - const initialDownloadState: DownloadState = { - modelId, - fileName, - percent: 0, - size: { - total: 0, - transferred: 0, - }, - children: [], - downloadState: 'downloading', - extensionId: downloadRequest.extensionId, - downloadType: downloadRequest.downloadType, - localPath: normalizedPath, - } - DownloadManager.instance.downloadProgressMap[modelId] = initialDownloadState - DownloadManager.instance.downloadInfo[normalizedPath] = initialDownloadState - - if (downloadRequest.downloadType === 'extension') { - observer?.(DownloadEvent.onFileDownloadUpdate, initialDownloadState) - } - - progress(rq, {}) - .on('progress', (state: any) => { - const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId] - const downloadState: DownloadState = { - ...currentDownloadState, - ...state, - fileName: fileName, - downloadState: 'downloading', - } - console.debug('progress: ', downloadState) - observer?.(DownloadEvent.onFileDownloadUpdate, downloadState) - DownloadManager.instance.downloadProgressMap[modelId] = downloadState - }) - .on('error', (error: Error) => { - const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId] - const downloadState: DownloadState = { - ...currentDownloadState, - fileName: fileName, - error: error.message, - downloadState: 'error', - } - - observer?.(DownloadEvent.onFileDownloadError, downloadState) - DownloadManager.instance.downloadProgressMap[modelId] = downloadState - }) - .on('end', () => { - const currentDownloadState = DownloadManager.instance.downloadProgressMap[modelId] - if ( - currentDownloadState && - DownloadManager.instance.networkRequests[normalizedPath] && - DownloadManager.instance.downloadProgressMap[modelId]?.downloadState !== 'error' - ) { - // Finished downloading, rename temp file to actual file - renameSync(downloadingTempFile, destination) - const downloadState: DownloadState = { - ...currentDownloadState, - fileName: fileName, - downloadState: 'end', - } - observer?.(DownloadEvent.onFileDownloadSuccess, downloadState) - DownloadManager.instance.downloadProgressMap[modelId] = downloadState - } - }) - .pipe(createWriteStream(downloadingTempFile)) - } - - abortDownload(observer: any, fileName: string) { - const rq = DownloadManager.instance.networkRequests[fileName] - if (rq) { - DownloadManager.instance.networkRequests[fileName] = undefined - rq?.abort() - } - - const downloadInfo = DownloadManager.instance.downloadInfo[fileName] - observer?.(DownloadEvent.onFileDownloadError, { - ...downloadInfo, - fileName, - error: 'aborted', - }) - } - - resumeDownload(_observer: any, fileName: any) { - DownloadManager.instance.networkRequests[fileName]?.resume() - } - - pauseDownload(_observer: any, fileName: any) { - DownloadManager.instance.networkRequests[fileName]?.pause() - } -} diff --git a/core/src/node/helper/download.test.ts b/core/src/node/helper/download.test.ts deleted file mode 100644 index 95cc553b5..000000000 --- a/core/src/node/helper/download.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DownloadManager } from './download'; - -it('should set a network request for a specific file', () => { - const downloadManager = new DownloadManager(); - const fileName = 'testFile'; - const request = { url: 'http://example.com' }; - - downloadManager.setRequest(fileName, request); - - expect(downloadManager.networkRequests[fileName]).toEqual(request); -}); diff --git a/core/src/node/helper/download.ts b/core/src/node/helper/download.ts deleted file mode 100644 index 51a0b0a8f..000000000 --- a/core/src/node/helper/download.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { DownloadState } from '../../types' - -/** - * Manages file downloads and network requests. - */ -export class DownloadManager { - public networkRequests: Record = {} - - public static instance: DownloadManager = new DownloadManager() - - // store the download information with key is model id - public downloadProgressMap: Record = {} - - // store the download information with key is normalized file path - public downloadInfo: Record = {} - - constructor() { - if (DownloadManager.instance) { - return DownloadManager.instance - } - } - /** - * Sets a network request for a specific file. - * @param {string} fileName - The name of the file. - * @param {Request | undefined} request - The network request to set, or undefined to clear the request. - */ - setRequest(fileName: string, request: any | undefined) { - this.networkRequests[fileName] = request - } -} diff --git a/core/src/node/helper/index.ts b/core/src/node/helper/index.ts index 51030023f..6464fbce2 100644 --- a/core/src/node/helper/index.ts +++ b/core/src/node/helper/index.ts @@ -1,5 +1,4 @@ export * from './config' -export * from './download' export * from './logger' export * from './module' export * from './path' diff --git a/core/src/types/api/index.ts b/core/src/types/api/index.ts index 84f91457a..a663f2674 100644 --- a/core/src/types/api/index.ts +++ b/core/src/types/api/index.ts @@ -65,30 +65,13 @@ export enum AppEvent { onMainViewStateChange = 'onMainViewStateChange', } -export enum DownloadRoute { - abortDownload = 'abortDownload', - downloadFile = 'downloadFile', - pauseDownload = 'pauseDownload', - resumeDownload = 'resumeDownload', - getDownloadProgress = 'getDownloadProgress', -} - export enum DownloadEvent { onFileDownloadUpdate = 'onFileDownloadUpdate', onFileDownloadError = 'onFileDownloadError', onFileDownloadSuccess = 'onFileDownloadSuccess', onFileDownloadStopped = 'onFileDownloadStopped', onFileDownloadStarted = 'onFileDownloadStarted', - onFileUnzipSuccess = 'onFileUnzipSuccess', } - -export enum LocalImportModelEvent { - onLocalImportModelUpdate = 'onLocalImportModelUpdate', - onLocalImportModelFailed = 'onLocalImportModelFailed', - onLocalImportModelSuccess = 'onLocalImportModelSuccess', - onLocalImportModelFinished = 'onLocalImportModelFinished', -} - export enum ExtensionRoute { baseExtensions = 'baseExtensions', getActiveExtensions = 'getActiveExtensions', @@ -131,10 +114,6 @@ export type AppEventFunctions = { [K in AppEvent]: ApiFunction } -export type DownloadRouteFunctions = { - [K in DownloadRoute]: ApiFunction -} - export type DownloadEventFunctions = { [K in DownloadEvent]: ApiFunction } @@ -154,7 +133,6 @@ export type FileManagerRouteFunctions = { export type APIFunctions = NativeRouteFunctions & AppRouteFunctions & AppEventFunctions & - DownloadRouteFunctions & DownloadEventFunctions & ExtensionRouteFunctions & FileSystemRouteFunctions & @@ -162,7 +140,6 @@ export type APIFunctions = NativeRouteFunctions & export const CoreRoutes = [ ...Object.values(AppRoute), - ...Object.values(DownloadRoute), ...Object.values(ExtensionRoute), ...Object.values(FileSystemRoute), ...Object.values(FileManagerRoute), @@ -172,7 +149,6 @@ export const APIRoutes = [...CoreRoutes, ...Object.values(NativeRoute)] export const APIEvents = [ ...Object.values(AppEvent), ...Object.values(DownloadEvent), - ...Object.values(LocalImportModelEvent), ] export type PayloadType = { messages: ChatCompletionMessage[] diff --git a/core/src/types/file/index.ts b/core/src/types/file/index.ts index 87d83c51d..3535dc6da 100644 --- a/core/src/types/file/index.ts +++ b/core/src/types/file/index.ts @@ -16,41 +16,9 @@ export type DownloadState = { error?: string extensionId?: string - downloadType?: DownloadType | string localPath?: string } -export type DownloadType = 'model' | 'extension' - -export type DownloadRequest = { - /** - * The URL to download the file from. - */ - url: string - - /** - * The local path to save the file to. - */ - localPath: string - - /** - * The extension ID of the extension that initiated the download. - * - * Can be extension name. - */ - extensionId?: string - - /** - * The model ID of the model that initiated the download. - */ - modelId?: string - - /** - * The download type. - */ - downloadType?: DownloadType | string -} - type DownloadTime = { elapsed: number remaining: number @@ -60,7 +28,6 @@ type DownloadSize = { total: number transferred: number } - /** * The file metadata */ diff --git a/core/src/types/miscellaneous/fileDownloadRequest.ts b/core/src/types/miscellaneous/fileDownloadRequest.ts deleted file mode 100644 index 83131aa71..000000000 --- a/core/src/types/miscellaneous/fileDownloadRequest.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type FileDownloadRequest = { - downloadId: string - url: string - localPath: string - fileName: string - displayName: string - metadata: Record -} diff --git a/core/src/types/miscellaneous/index.ts b/core/src/types/miscellaneous/index.ts index 2693ffd8b..6e533259d 100644 --- a/core/src/types/miscellaneous/index.ts +++ b/core/src/types/miscellaneous/index.ts @@ -1,6 +1,4 @@ export * from './systemResourceInfo' export * from './promptTemplate' export * from './appUpdate' -export * from './fileDownloadRequest' -export * from './networkConfig' export * from './selectFiles' diff --git a/core/src/types/miscellaneous/networkConfig.ts b/core/src/types/miscellaneous/networkConfig.ts deleted file mode 100644 index 2d27f4223..000000000 --- a/core/src/types/miscellaneous/networkConfig.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type NetworkConfig = { - proxy?: string - ignoreSSL?: boolean -} diff --git a/core/src/types/thread/index.ts b/core/src/types/thread/index.ts index 32155e1cd..c6ff6204a 100644 --- a/core/src/types/thread/index.ts +++ b/core/src/types/thread/index.ts @@ -1,3 +1,2 @@ export * from './threadEntity' export * from './threadInterface' -export * from './threadEvent' diff --git a/core/src/types/thread/threadEvent.test.ts b/core/src/types/thread/threadEvent.test.ts deleted file mode 100644 index f892f1050..000000000 --- a/core/src/types/thread/threadEvent.test.ts +++ /dev/null @@ -1,6 +0,0 @@ - -import { ThreadEvent } from './threadEvent'; - -it('should have the correct values', () => { - expect(ThreadEvent.OnThreadStarted).toBe('OnThreadStarted'); -}); diff --git a/core/src/types/thread/threadEvent.ts b/core/src/types/thread/threadEvent.ts deleted file mode 100644 index 4b19b09c1..000000000 --- a/core/src/types/thread/threadEvent.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum ThreadEvent { - /** The `OnThreadStarted` event is emitted when a thread is started. */ - OnThreadStarted = 'OnThreadStarted', -} diff --git a/electron/package.json b/electron/package.json index e97ef4e7f..660a5e264 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "jan", - "version": "0.1.1", + "version": "0.1.1740752217", "main": "./build/main.js", "author": "Jan ", "license": "MIT", @@ -113,7 +113,6 @@ "electron-store": "^8.1.0", "electron-updater": "^6.1.7", "fs-extra": "^11.2.0", - "node-fetch": "2", "pacote": "^21.0.0", "request": "^2.88.2", "request-progress": "^3.0.0", diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index c80adf72c..94ed2b7a1 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -5,11 +5,6 @@ import { joinPath, dirName, fs, - ModelManager, - abortDownload, - DownloadState, - events, - DownloadEvent, OptionType, ModelSource, extractInferenceParams, @@ -55,9 +50,6 @@ export default class JanModelExtension extends ModelExtension { this.updateCortexConfig({ huggingface_token: huggingfaceToken }) } - // Listen to app download events - this.handleDesktopEvents() - // Sync with cortexsohub this.fetchCortexsoModels() } @@ -107,21 +99,6 @@ export default class JanModelExtension extends ModelExtension { * @returns {Promise} A promise that resolves when the download has been cancelled. */ async cancelModelPull(model: string): Promise { - if (model) { - const modelDto: Model = ModelManager.instance().get(model) - // Clip vision model - should not be handled by cortex.cpp - // TensorRT model - should not be handled by cortex.cpp - if ( - modelDto && - (modelDto.engine === InferenceEngine.nitro_tensorrt_llm || - modelDto.settings.vision_model) - ) { - for (const source of modelDto.sources) { - const path = await joinPath(['models', modelDto.id, source.filename]) - await abortDownload(path) - } - } - } /** * Sending DELETE to /models/pull/{id} endpoint to cancel a model pull */ @@ -362,32 +339,6 @@ export default class JanModelExtension extends ModelExtension { // END: - Public API // BEGIN: - Private API - /** - * Handle download state from main app - */ - private handleDesktopEvents() { - if (window && window.electronAPI) { - window.electronAPI.onFileDownloadUpdate( - async (_event: string, state: DownloadState | undefined) => { - if (!state) return - state.downloadState = 'downloading' - events.emit(DownloadEvent.onFileDownloadUpdate, state) - } - ) - window.electronAPI.onFileDownloadError( - async (_event: string, state: DownloadState) => { - state.downloadState = 'error' - events.emit(DownloadEvent.onFileDownloadError, state) - } - ) - window.electronAPI.onFileDownloadSuccess( - async (_event: string, state: DownloadState) => { - state.downloadState = 'end' - events.emit(DownloadEvent.onFileDownloadSuccess, state) - } - ) - } - } /** * Transform model to the expected format (e.g. parameters, settings, metadata) diff --git a/extensions/model-extension/src/legacy/download.ts b/extensions/model-extension/src/legacy/download.ts deleted file mode 100644 index 570d0cd13..000000000 --- a/extensions/model-extension/src/legacy/download.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { - downloadFile, - DownloadRequest, - fs, - joinPath, - Model, -} from '@janhq/core' - -export const downloadModel = async ( - model: Model, - network?: { ignoreSSL?: boolean; proxy?: string } -): Promise => { - const homedir = 'file://models' - const supportedGpuArch = ['ampere', 'ada'] - // Create corresponding directory - const modelDirPath = await joinPath([homedir, model.id]) - if (!(await fs.existsSync(modelDirPath))) await fs.mkdir(modelDirPath) - - const jsonFilePath = await joinPath([modelDirPath, 'model.json']) - // Write model.json on download - if (!(await fs.existsSync(jsonFilePath))) - await fs.writeFileSync( - jsonFilePath, - JSON.stringify(model, null, 2) - ) - - console.debug(`Download sources: ${JSON.stringify(model.sources)}`) - - if (model.sources.length > 1) { - // path to model binaries - for (const source of model.sources) { - let path = extractFileName(source.url, '.gguf') - if (source.filename) { - path = await joinPath([modelDirPath, source.filename]) - } - - const downloadRequest: DownloadRequest = { - url: source.url, - localPath: path, - modelId: model.id, - } - downloadFile(downloadRequest, network) - } - } else { - const fileName = extractFileName(model.sources[0]?.url, '.gguf') - const path = await joinPath([modelDirPath, fileName]) - const downloadRequest: DownloadRequest = { - url: model.sources[0]?.url, - localPath: path, - modelId: model.id, - } - downloadFile(downloadRequest, network) - } -} - -/** - * try to retrieve the download file name from the source url - */ -function extractFileName(url: string, fileExtension: string): string { - if (!url) return fileExtension - - const extractedFileName = url.split('/').pop() - const fileName = extractedFileName.toLowerCase().endsWith(fileExtension) - ? extractedFileName - : extractedFileName + fileExtension - return fileName -} diff --git a/web/containers/Layout/BottomPanel/InstallingExtension/InstallingExtensionModal.tsx b/web/containers/Layout/BottomPanel/InstallingExtension/InstallingExtensionModal.tsx deleted file mode 100644 index 0d5e4d4e3..000000000 --- a/web/containers/Layout/BottomPanel/InstallingExtension/InstallingExtensionModal.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useCallback, useEffect } from 'react' - -import { abortDownload } from '@janhq/core' -import { Button, Modal, Progress } from '@janhq/joi' -import { atom, useAtom, useAtomValue } from 'jotai' - -import { - formatDownloadPercentage, - formatExtensionsName, -} from '@/utils/converter' - -import { - InstallingExtensionState, - installingExtensionAtom, -} from '@/helpers/atoms/Extension.atom' - -export const showInstallingExtensionModalAtom = atom(false) - -const InstallingExtensionModal = () => { - const [showInstallingExtensionModal, setShowInstallingExtensionModal] = - useAtom(showInstallingExtensionModalAtom) - const installingExtensions = useAtomValue(installingExtensionAtom) - - useEffect(() => { - if (installingExtensions.length === 0) { - setShowInstallingExtensionModal(false) - } - }, [installingExtensions, setShowInstallingExtensionModal]) - - const onAbortInstallingExtensionClick = useCallback( - (item: InstallingExtensionState) => { - if (item.localPath) { - abortDownload(item.localPath) - } - }, - [] - ) - - return ( - setShowInstallingExtensionModal(false)} - content={ -
- {Object.values(installingExtensions).map((item) => ( -
- -
-
-

- {formatExtensionsName(item.extensionId)} -

- {formatDownloadPercentage(item.percentage)} -
- -
-
- ))} -
- } - /> - ) -} - -export default InstallingExtensionModal diff --git a/web/containers/Layout/BottomPanel/InstallingExtension/index.tsx b/web/containers/Layout/BottomPanel/InstallingExtension/index.tsx deleted file mode 100644 index b41b64e22..000000000 --- a/web/containers/Layout/BottomPanel/InstallingExtension/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Fragment, useCallback } from 'react' - -import { Progress } from '@janhq/joi' -import { useAtomValue, useSetAtom } from 'jotai' - -import { showInstallingExtensionModalAtom } from './InstallingExtensionModal' - -import { installingExtensionAtom } from '@/helpers/atoms/Extension.atom' - -const InstallingExtension = () => { - const installingExtensions = useAtomValue(installingExtensionAtom) - const setShowInstallingExtensionModal = useSetAtom( - showInstallingExtensionModalAtom - ) - const shouldShowInstalling = installingExtensions.length > 0 - - let totalPercentage = 0 - let totalExtensions = 0 - for (const installation of installingExtensions) { - totalPercentage += installation.percentage - totalExtensions++ - } - const progress = (totalPercentage / totalExtensions) * 100 - - const onClick = useCallback(() => { - setShowInstallingExtensionModal(true) - }, [setShowInstallingExtensionModal]) - - return ( - - {shouldShowInstalling ? ( -
-

- Installing Additional Dependencies -

- -
- - - {progress.toFixed(2)}% - -
-
- ) : null} -
- ) -} - -export default InstallingExtension diff --git a/web/containers/Layout/BottomPanel/index.tsx b/web/containers/Layout/BottomPanel/index.tsx index 69894c9e3..90d9d84ba 100644 --- a/web/containers/Layout/BottomPanel/index.tsx +++ b/web/containers/Layout/BottomPanel/index.tsx @@ -7,7 +7,6 @@ import { twMerge } from 'tailwind-merge' import DownloadingState from './DownloadingState' import ImportingModelState from './ImportingModelState' -import InstallingExtension from './InstallingExtension' import SystemMonitor from './SystemMonitor' import UpdateApp from './UpdateApp' import UpdatedFailedModal from './UpdateFailedModal' @@ -49,7 +48,6 @@ const BottomPanel = () => { -
diff --git a/web/containers/Layout/index.tsx b/web/containers/Layout/index.tsx index 024ddfca4..db675d5eb 100644 --- a/web/containers/Layout/index.tsx +++ b/web/containers/Layout/index.tsx @@ -35,8 +35,6 @@ import ModalAppUpdaterChangelog from '../ModalAppUpdaterChangelog' import ModalAppUpdaterNotAvailable from '../ModalAppUpdaterNotAvailable' -import InstallingExtensionModal from './BottomPanel/InstallingExtension/InstallingExtensionModal' - import { mainViewStateAtom } from '@/helpers/atoms/App.atom' import { productAnalyticAtom, @@ -167,7 +165,6 @@ const BaseLayout = () => { {importModelStage === 'EDIT_MODEL_INFO' && } {importModelStage === 'CONFIRM_CANCEL' && } - {showProductAnalyticPrompt && (
diff --git a/web/containers/Providers/EventListener.tsx b/web/containers/Providers/EventListener.tsx index 55c172beb..27764bcdc 100644 --- a/web/containers/Providers/EventListener.tsx +++ b/web/containers/Providers/EventListener.tsx @@ -4,8 +4,8 @@ import React from 'react' import { DownloadEvent, - events, DownloadState, + events, ModelEvent, ExtensionTypeEnum, ModelExtension, @@ -17,23 +17,15 @@ import { useSetAtom } from 'jotai' import { setDownloadStateAtom } from '@/hooks/useDownloadState' -import { formatExtensionsName } from '@/utils/converter' - import { toaster } from '../Toast' import AppUpdateListener from './AppUpdateListener' import ClipboardListener from './ClipboardListener' import ModelHandler from './ModelHandler' -import ModelImportListener from './ModelImportListener' import QuickAskListener from './QuickAskListener' import { extensionManager } from '@/extension' -import { - InstallingExtensionState, - removeInstallingExtensionAtom, - setInstallingExtensionAtom, -} from '@/helpers/atoms/Extension.atom' import { addDownloadingModelAtom, removeDownloadingModelAtom, @@ -41,109 +33,76 @@ import { const EventListener = () => { const setDownloadState = useSetAtom(setDownloadStateAtom) - const setInstallingExtension = useSetAtom(setInstallingExtensionAtom) - const removeInstallingExtension = useSetAtom(removeInstallingExtensionAtom) const addDownloadingModel = useSetAtom(addDownloadingModelAtom) const removeDownloadingModel = useSetAtom(removeDownloadingModelAtom) const onFileDownloadUpdate = useCallback( async (state: DownloadState) => { console.debug('onFileDownloadUpdate', state) - if (state.downloadType === 'extension') { - const installingExtensionState: InstallingExtensionState = { - extensionId: state.extensionId!, - percentage: state.percent, - localPath: state.localPath, - } - setInstallingExtension(state.extensionId!, installingExtensionState) - } else { - addDownloadingModel(state.modelId) - setDownloadState(state) - } + addDownloadingModel(state.modelId) + setDownloadState(state) }, - [addDownloadingModel, setDownloadState, setInstallingExtension] + [addDownloadingModel, setDownloadState] ) const onFileDownloadError = useCallback( (state: DownloadState) => { console.debug('onFileDownloadError', state) - if (state.downloadType === 'extension') { - removeInstallingExtension(state.extensionId!) - } else { - state.downloadState = 'error' - setDownloadState(state) - removeDownloadingModel(state.modelId) - } + state.downloadState = 'error' + setDownloadState(state) + removeDownloadingModel(state.modelId) }, - [removeInstallingExtension, setDownloadState, removeDownloadingModel] + [setDownloadState, removeDownloadingModel] ) const onFileDownloadStopped = useCallback( (state: DownloadState) => { console.debug('onFileDownloadError', state) - if (state.downloadType === 'extension') { - removeInstallingExtension(state.extensionId!) - } else { - state.downloadState = 'error' - state.error = 'aborted' - setDownloadState(state) - removeDownloadingModel(state.modelId) - } + + state.downloadState = 'error' + state.error = 'aborted' + setDownloadState(state) + removeDownloadingModel(state.modelId) }, - [removeInstallingExtension, setDownloadState, removeDownloadingModel] + [setDownloadState, removeDownloadingModel] ) const onFileDownloadSuccess = useCallback( async (state: DownloadState) => { console.debug('onFileDownloadSuccess', state) - if (state.downloadType !== 'extension') { - // Update model metadata accordingly - const model = ModelManager.instance().models.get(state.modelId) - if (model) { - await extensionManager - .get(ExtensionTypeEnum.Model) - ?.updateModel({ - id: model.id, - ...model.settings, - ...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) - removeDownloadingModel(state.modelId) - events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) + // Update model metadata accordingly + const model = ModelManager.instance().models.get(state.modelId) + if (model) { + await extensionManager + .get(ExtensionTypeEnum.Model) + ?.updateModel({ + id: model.id, + ...model.settings, + ...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) + removeDownloadingModel(state.modelId) + events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) }, [removeDownloadingModel, setDownloadState] ) - const onFileUnzipSuccess = useCallback( - (state: DownloadState) => { - console.debug('onFileUnzipSuccess', state) - toaster({ - title: 'Success', - description: `Install ${formatExtensionsName(state.extensionId!)} successfully.`, - type: 'success', - }) - removeInstallingExtension(state.extensionId!) - }, - [removeInstallingExtension] - ) - useEffect(() => { console.debug('EventListenerWrapper: registering event listeners...') events.on(DownloadEvent.onFileDownloadUpdate, onFileDownloadUpdate) events.on(DownloadEvent.onFileDownloadError, onFileDownloadError) events.on(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) events.on(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped) - events.on(DownloadEvent.onFileUnzipSuccess, onFileUnzipSuccess) return () => { console.debug('EventListenerWrapper: unregistering event listeners...') @@ -151,13 +110,11 @@ const EventListener = () => { events.off(DownloadEvent.onFileDownloadError, onFileDownloadError) events.off(DownloadEvent.onFileDownloadSuccess, onFileDownloadSuccess) events.off(DownloadEvent.onFileDownloadStopped, onFileDownloadStopped) - events.off(DownloadEvent.onFileUnzipSuccess, onFileUnzipSuccess) } }, [ onFileDownloadUpdate, onFileDownloadError, onFileDownloadSuccess, - onFileUnzipSuccess, onFileDownloadStopped, ]) @@ -165,7 +122,6 @@ const EventListener = () => { <> - diff --git a/web/containers/Providers/ModelImportListener.tsx b/web/containers/Providers/ModelImportListener.tsx deleted file mode 100644 index e99b1e6fc..000000000 --- a/web/containers/Providers/ModelImportListener.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Fragment, useCallback, useEffect } from 'react' - -import { - ImportingModel, - LocalImportModelEvent, - Model, - ModelEvent, - events, -} from '@janhq/core' -import { useSetAtom } from 'jotai' - -import { snackbar } from '../Toast' - -import { - setImportingModelErrorAtom, - setImportingModelSuccessAtom, - updateImportingModelProgressAtom, -} from '@/helpers/atoms/Model.atom' - -const ModelImportListener = () => { - const updateImportingModelProgress = useSetAtom( - updateImportingModelProgressAtom - ) - const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom) - const setImportingModelFailed = useSetAtom(setImportingModelErrorAtom) - - const onImportModelUpdate = useCallback( - async (state: ImportingModel) => { - if (!state.importId) return - updateImportingModelProgress(state.importId, state.percentage ?? 0) - }, - [updateImportingModelProgress] - ) - - const onImportModelFailed = useCallback( - async (state: ImportingModel) => { - if (!state.importId) return - setImportingModelFailed(state.importId, state.error ?? '') - }, - [setImportingModelFailed] - ) - - const onImportModelSuccess = useCallback( - (state: ImportingModel) => { - if (!state.modelId) return - events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) - setImportingModelSuccess(state.importId, state.modelId) - }, - [setImportingModelSuccess] - ) - - const onImportModelFinished = useCallback((importedModels: Model[]) => { - const modelText = importedModels.length === 1 ? 'model' : 'models' - snackbar({ - description: `Successfully imported ${importedModels.length} ${modelText}`, - type: 'success', - }) - }, []) - - useEffect(() => { - console.debug('ModelImportListener: registering event listeners..') - - events.on( - LocalImportModelEvent.onLocalImportModelUpdate, - onImportModelUpdate - ) - events.on( - LocalImportModelEvent.onLocalImportModelSuccess, - onImportModelSuccess - ) - events.on( - LocalImportModelEvent.onLocalImportModelFinished, - onImportModelFinished - ) - events.on( - LocalImportModelEvent.onLocalImportModelFailed, - onImportModelFailed - ) - - return () => { - console.debug('ModelImportListener: unregistering event listeners...') - events.off( - LocalImportModelEvent.onLocalImportModelUpdate, - onImportModelUpdate - ) - events.off( - LocalImportModelEvent.onLocalImportModelSuccess, - onImportModelSuccess - ) - events.off( - LocalImportModelEvent.onLocalImportModelFinished, - onImportModelFinished - ) - events.off( - LocalImportModelEvent.onLocalImportModelFailed, - onImportModelFailed - ) - } - }, [ - onImportModelUpdate, - onImportModelSuccess, - onImportModelFinished, - onImportModelFailed, - ]) - - return -} - -export default ModelImportListener diff --git a/web/helpers/atoms/Extension.atom.test.ts b/web/helpers/atoms/Extension.atom.test.ts index 2a12cd7db..d41290eea 100644 --- a/web/helpers/atoms/Extension.atom.test.ts +++ b/web/helpers/atoms/Extension.atom.test.ts @@ -9,54 +9,6 @@ describe('Extension.atom.ts', () => { jest.clearAllMocks() }) - describe('installingExtensionAtom', () => { - it('should initialize as an empty array', () => { - const { result } = renderHook(() => useAtomValue(ExtensionAtoms.installingExtensionAtom)) - expect(result.current).toEqual([]) - }) - }) - - describe('setInstallingExtensionAtom', () => { - it('should add a new installing extension', () => { - const { result: setAtom } = renderHook(() => useSetAtom(ExtensionAtoms.setInstallingExtensionAtom)) - const { result: getAtom } = renderHook(() => useAtomValue(ExtensionAtoms.installingExtensionAtom)) - - act(() => { - setAtom.current('ext1', { extensionId: 'ext1', percentage: 0 }) - }) - - expect(getAtom.current).toEqual([{ extensionId: 'ext1', percentage: 0 }]) - }) - - it('should update an existing installing extension', () => { - const { result: setAtom } = renderHook(() => useSetAtom(ExtensionAtoms.setInstallingExtensionAtom)) - const { result: getAtom } = renderHook(() => useAtomValue(ExtensionAtoms.installingExtensionAtom)) - - act(() => { - setAtom.current('ext1', { extensionId: 'ext1', percentage: 0 }) - setAtom.current('ext1', { extensionId: 'ext1', percentage: 50 }) - }) - - expect(getAtom.current).toEqual([{ extensionId: 'ext1', percentage: 50 }]) - }) - }) - - describe('removeInstallingExtensionAtom', () => { - it('should remove an installing extension', () => { - const { result: setAtom } = renderHook(() => useSetAtom(ExtensionAtoms.setInstallingExtensionAtom)) - const { result: removeAtom } = renderHook(() => useSetAtom(ExtensionAtoms.removeInstallingExtensionAtom)) - const { result: getAtom } = renderHook(() => useAtomValue(ExtensionAtoms.installingExtensionAtom)) - - act(() => { - setAtom.current('ext1', { extensionId: 'ext1', percentage: 0 }) - setAtom.current('ext2', { extensionId: 'ext2', percentage: 50 }) - removeAtom.current('ext1') - }) - - expect(getAtom.current).toEqual([{ extensionId: 'ext2', percentage: 50 }]) - }) - }) - describe('inActiveEngineProviderAtom', () => { it('should initialize as an empty array', () => { const { result } = renderHook(() => useAtomValue(ExtensionAtoms.inActiveEngineProviderAtom)) diff --git a/web/helpers/atoms/Extension.atom.ts b/web/helpers/atoms/Extension.atom.ts index 3f1843dc4..7e008df85 100644 --- a/web/helpers/atoms/Extension.atom.ts +++ b/web/helpers/atoms/Extension.atom.ts @@ -1,45 +1,5 @@ -import { atom } from 'jotai' import { atomWithStorage } from 'jotai/utils' -type ExtensionId = string - -export type InstallingExtensionState = { - extensionId: ExtensionId - percentage: number - localPath?: string -} - -export const installingExtensionAtom = atom([]) - -export const setInstallingExtensionAtom = atom( - null, - (get, set, extensionId: string, state: InstallingExtensionState) => { - const current = get(installingExtensionAtom) - - const isExists = current.some((e) => e.extensionId === extensionId) - if (isExists) { - const newCurrent = current.map((e) => { - if (e.extensionId === extensionId) { - return state - } - return e - }) - set(installingExtensionAtom, newCurrent) - } else { - set(installingExtensionAtom, [...current, state]) - } - } -) - -export const removeInstallingExtensionAtom = atom( - null, - (get, set, extensionId: string) => { - const current = get(installingExtensionAtom) - const newCurrent = current.filter((e) => e.extensionId !== extensionId) - set(installingExtensionAtom, newCurrent) - } -) - const INACTIVE_ENGINE_PROVIDER = 'inActiveEngineProvider' export const inActiveEngineProviderAtom = atomWithStorage( INACTIVE_ENGINE_PROVIDER, diff --git a/web/hooks/useImportModel.ts b/web/hooks/useImportModel.ts index 84c6a5126..94d11b455 100644 --- a/web/hooks/useImportModel.ts +++ b/web/hooks/useImportModel.ts @@ -3,8 +3,8 @@ import { useCallback } from 'react' import { ExtensionTypeEnum, ImportingModel, - LocalImportModelEvent, Model, + ModelEvent, ModelExtension, OptionType, events, @@ -25,6 +25,7 @@ import { downloadedModelsAtom, importingModelsAtom, removeDownloadingModelAtom, + setImportingModelSuccessAtom, } from '@/helpers/atoms/Model.atom' export type ImportModelStage = @@ -59,6 +60,7 @@ const useImportModel = () => { const addDownloadingModel = useSetAtom(addDownloadingModelAtom) const removeDownloadingModel = useSetAtom(removeDownloadingModelAtom) const downloadedModels = useAtomValue(downloadedModelsAtom) + const setImportingModelSuccess = useSetAtom(setImportingModelSuccessAtom) const incrementalModelName = useCallback( (name: string, startIndex: number = 0): string => { @@ -83,10 +85,9 @@ const useImportModel = () => { ?.importModel(modelId, model.path, model.name, optionType) .finally(() => { removeDownloadingModel(modelId) - events.emit(LocalImportModelEvent.onLocalImportModelSuccess, { - importId: model.importId, - modelId: modelId, - }) + + events.emit(ModelEvent.OnModelsUpdate, { fetch: true }) + setImportingModelSuccess(model.importId, modelId) }) } }) diff --git a/web/screens/Settings/CoreExtensions/ExtensionItem.tsx b/web/screens/Settings/CoreExtensions/ExtensionItem.tsx deleted file mode 100644 index 497b8ac4a..000000000 --- a/web/screens/Settings/CoreExtensions/ExtensionItem.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' - -import { - BaseExtension, - Compatibility, - InstallationState, - abortDownload, -} from '@janhq/core' -import { Button, Progress, Tooltip } from '@janhq/joi' - -import { InfoCircledIcon } from '@radix-ui/react-icons' -import { useAtomValue } from 'jotai' - -import { Marked, Renderer } from 'marked' - -import { extensionManager } from '@/extension' -import { installingExtensionAtom } from '@/helpers/atoms/Extension.atom' - -type Props = { - item: BaseExtension -} - -const ExtensionItem: React.FC = ({ item }) => { - const [compatibility, setCompatibility] = useState( - undefined - ) - const [installState, setInstallState] = - useState('NotRequired') - const installingExtensions = useAtomValue(installingExtensionAtom) - const isInstalling = installingExtensions.some( - (e) => e.extensionId === item.name - ) - - const progress = isInstalling - ? (installingExtensions.find((e) => e.extensionId === item.name) - ?.percentage ?? -1) - : -1 - - useEffect(() => { - const getExtensionInstallationState = async () => { - const extension = extensionManager.getByName(item.name) - if (!extension) return - - if (typeof extension?.installationState === 'function') { - const installState = await extension.installationState() - setInstallState(installState) - } - } - - getExtensionInstallationState() - }, [item.name, isInstalling]) - - useEffect(() => { - const extension = extensionManager.getByName(item.name) - if (!extension) return - setCompatibility(extension.compatibility()) - }, [setCompatibility, item.name]) - - const onInstallClick = useCallback(async () => { - const extension = extensionManager.getByName(item.name) - if (!extension) return - - await extension.install() - }, [item.name]) - - const onCancelInstallingClick = () => { - const extension = installingExtensions.find( - (e) => e.extensionId === item.name - ) - if (extension?.localPath) { - abortDownload(extension.localPath) - } - } - - const description = marked.parse(item.description ?? '', { async: false }) - - return ( -
-
-
-
Additional Dependencies
-
-
-
- -
- -
-
- ) -} - -type InstallStateProps = { - installProgress: number - compatibility?: Compatibility - installState: InstallationState - onInstallClick: () => void - onCancelClick: () => void -} - -const InstallStateIndicator: React.FC = ({ - installProgress, - compatibility, - installState, - onInstallClick, - onCancelClick, -}) => { - if (installProgress !== -1) { - const progress = installProgress * 100 - return ( -
- -
- - - {progress.toFixed(0)}% - -
-
- ) - } - - switch (installState) { - case 'Installed': - return ( -
- Installed -
- ) - case 'NotCompatible': - return ( -
-
- Incompatible - - } - content={ - compatibility && - !compatibility['platform']?.includes(PLATFORM) ? ( - - Only available on  - {compatibility?.platform - ?.map((e: string) => - e === 'win32' - ? 'Windows' - : e === 'linux' - ? 'Linux' - : 'MacOS' - ) - .join(', ')} - - ) : ( - Your GPUs are not compatible with this extension - ) - } - /> -
-
- ) - case 'NotInstalled': - return ( - - ) - default: - return
- } -} - -const marked: Marked = new Marked({ - renderer: { - link: (href, title, text) => { - return Renderer.prototype.link - ?.apply(this, [href, title, text]) - .replace( - ' { 'provider' in extension && typeof extension.provider === 'string' ) { - if ( - (settings && settings.length > 0) || - (await extension.installationState()) !== 'NotRequired' - ) { + if (settings && settings.length > 0) { engineMenu.push({ ...extension, provider: diff --git a/web/screens/Settings/ExtensionSetting/index.tsx b/web/screens/Settings/ExtensionSetting/index.tsx index d73f1674d..2d134d3dd 100644 --- a/web/screens/Settings/ExtensionSetting/index.tsx +++ b/web/screens/Settings/ExtensionSetting/index.tsx @@ -1,14 +1,9 @@ import React, { Fragment, useEffect, useMemo, useState } from 'react' -import { - BaseExtension, - InstallationState, - SettingComponentProps, -} from '@janhq/core' +import { SettingComponentProps } from '@janhq/core' import { useAtomValue } from 'jotai' -import ExtensionItem from '../CoreExtensions/ExtensionItem' import SettingDetailItem from '../SettingDetail/SettingDetailItem' import { extensionManager } from '@/extension' @@ -17,11 +12,6 @@ import { selectedSettingAtom } from '@/helpers/atoms/Setting.atom' const ExtensionSetting = ({ extensionName }: { extensionName?: string }) => { const selectedExtensionName = useAtomValue(selectedSettingAtom) const [settings, setSettings] = useState([]) - const [installationState, setInstallationState] = - useState('NotRequired') - const [baseExtension, setBaseExtension] = useState( - undefined - ) const currentExtensionName = useMemo( () => extensionName ?? selectedExtensionName, @@ -35,14 +25,11 @@ const ExtensionSetting = ({ extensionName }: { extensionName?: string }) => { const baseExtension = extensionManager.getByName(currentExtensionName) if (!baseExtension) return - setBaseExtension(baseExtension) if (typeof baseExtension.getSettings === 'function') { const setting = await baseExtension.getSettings() if (setting) allSettings.push(...setting) } setSettings(allSettings) - - setInstallationState(await baseExtension.installationState()) } getExtensionSettings() }, [currentExtensionName]) @@ -75,9 +62,6 @@ const ExtensionSetting = ({ extensionName }: { extensionName?: string }) => { onValueUpdated={onValueChanged} /> )} - {baseExtension && installationState !== 'NotRequired' && ( - - )} ) }