diff --git a/.gitignore b/.gitignore index 9dfd1887c8..aa61b9cb38 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ dist build .DS_Store electron/renderer +electron/models package-lock.json *.log diff --git a/core/src/core.ts b/core/src/core.ts index b593277cb5..0e032f4d94 100644 --- a/core/src/core.ts +++ b/core/src/core.ts @@ -54,6 +54,9 @@ const getUserSpace = (): Promise => window.core.api?.getUserSpace(); const openFileExplorer: (path: string) => Promise = (path) => window.core.api?.openFileExplorer(path); +const getResourcePath: () => Promise = () => + window.core.api?.getResourcePath(); + /** * Register extension point function type definition */ @@ -74,4 +77,5 @@ export { appDataPath, getUserSpace, openFileExplorer, + getResourcePath, }; diff --git a/core/src/extensions/model.ts b/core/src/extensions/model.ts index 3a5cc1ba32..276d15dcce 100644 --- a/core/src/extensions/model.ts +++ b/core/src/extensions/model.ts @@ -1,5 +1,5 @@ import { BaseExtension } from "../extension"; -import { Model, ModelCatalog } from "../types/index"; +import { Model } from "../types/index"; /** * Model extension for managing models. @@ -43,5 +43,5 @@ export abstract class ModelExtension extends BaseExtension { * Gets a list of configured models. * @returns A Promise that resolves with an array of configured models. */ - abstract getConfiguredModels(): Promise; + abstract getConfiguredModels(): Promise; } diff --git a/core/src/fs.ts b/core/src/fs.ts index e8eb38e04e..d12b473bfb 100644 --- a/core/src/fs.ts +++ b/core/src/fs.ts @@ -62,6 +62,9 @@ const deleteFile: (path: string) => Promise = (path) => const appendFile: (path: string, data: string) => Promise = (path, data) => window.core.api?.appendFile(path, data); +const copyFile: (src: string, dest: string) => Promise = (src, dest) => + window.core.api?.copyFile(src, dest); + /** * Reads a file line by line. * @param {string} path - The path of the file to read. @@ -80,4 +83,5 @@ export const fs = { deleteFile, appendFile, readLineByLine, + copyFile, }; diff --git a/core/src/types/index.ts b/core/src/types/index.ts index 15e83772f9..bbd1e98dee 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -180,7 +180,7 @@ export interface Model { /** * The version of the model. */ - version: string; + version: number; /** * The model download source. It can be an external url or a local filepath. @@ -197,12 +197,6 @@ export interface Model { */ name: string; - /** - * The organization that owns the model (you!) - * Default: "you" - */ - owned_by: string; - /** * The Unix timestamp (in seconds) for when the model was created */ @@ -236,11 +230,16 @@ export interface Model { metadata: ModelMetadata; } +export type ModelMetadata = { + author: string; + tags: string[]; + size: number; +}; + /** * The Model transition states. */ export enum ModelState { - ToDownload = "to_download", Downloading = "downloading", Ready = "ready", Running = "running", @@ -250,65 +249,27 @@ export enum ModelState { * The available model settings. */ export type ModelSettingParams = { - ctx_len: number; - ngl: number; - embedding: boolean; - n_parallel: number; + ctx_len?: number; + ngl?: number; + embedding?: boolean; + n_parallel?: number; + system_prompt?: string; + user_prompt?: string; + ai_prompt?: string; }; /** * The available model runtime parameters. */ export type ModelRuntimeParam = { - temperature: number; - token_limit: number; - top_k: number; - top_p: number; - stream: boolean; + temperature?: number; + token_limit?: number; + top_k?: number; + top_p?: number; + stream?: boolean; + max_tokens?: number; }; -/** - * The metadata of the model. - */ -export type ModelMetadata = { - engine: string; - quantization: string; - size: number; - binaries: string[]; - maxRamRequired: number; - author: string; - avatarUrl: string; -}; - -/** - * Model type of the presentation object which will be presented to the user - * @data_transfer_object - */ -export interface ModelCatalog { - /** The unique id of the model.*/ - id: string; - /** The name of the model.*/ - name: string; - /** The avatar url of the model.*/ - avatarUrl: string; - /** The short description of the model.*/ - shortDescription: string; - /** The long description of the model.*/ - longDescription: string; - /** The author name of the model.*/ - author: string; - /** The version of the model.*/ - version: string; - /** The origin url of the model repo.*/ - modelUrl: string; - /** The timestamp indicating when this model was released.*/ - releaseDate: number; - /** The tags attached to the model description **/ - tags: string[]; - /** The available versions of this model to download. */ - availableVersions: Model[]; -} - /** * Assistant type defines the shape of an assistant object. * @stored diff --git a/electron/handlers/app.ts b/electron/handlers/app.ts index 4a6d56b6ab..adbc875b2e 100644 --- a/electron/handlers/app.ts +++ b/electron/handlers/app.ts @@ -1,9 +1,9 @@ import { app, ipcMain, shell } from 'electron' -import { ModuleManager } from '../managers/module' +import { ModuleManager } from './../managers/module' import { join } from 'path' -import { ExtensionManager } from '../managers/extension' -import { WindowManager } from '../managers/window' -import { userSpacePath } from '../utils/path' +import { ExtensionManager } from './../managers/extension' +import { WindowManager } from './../managers/window' +import { userSpacePath } from './../utils/path' export function handleAppIPCs() { /** diff --git a/electron/handlers/download.ts b/electron/handlers/download.ts index 316576e896..1776fccd9a 100644 --- a/electron/handlers/download.ts +++ b/electron/handlers/download.ts @@ -1,9 +1,10 @@ import { app, ipcMain } from 'electron' -import { DownloadManager } from '../managers/download' +import { DownloadManager } from './../managers/download' import { resolve, join } from 'path' -import { WindowManager } from '../managers/window' +import { WindowManager } from './../managers/window' import request from 'request' -import { createWriteStream, unlink } from 'fs' +import { createWriteStream } from 'fs' +import { getResourcePath } from './../utils/path' const progress = require('request-progress') export function handleDownloaderIPCs() { @@ -37,6 +38,10 @@ export function handleDownloaderIPCs() { rq?.abort() }) + ipcMain.handle('getResourcePath', async (_event) => { + return getResourcePath() + }) + /** * Downloads a file from a given URL. * @param _event - The IPC event object. diff --git a/electron/handlers/extension.ts b/electron/handlers/extension.ts index 1af1be36c8..5c2c13ff4f 100644 --- a/electron/handlers/extension.ts +++ b/electron/handlers/extension.ts @@ -1,19 +1,16 @@ -import { app, ipcMain, webContents } from 'electron' -import { readdirSync, rmdir, writeFileSync } from 'fs' -import { ModuleManager } from '../managers/module' +import { ipcMain, webContents } from 'electron' +import { readdirSync } from 'fs' +import { ModuleManager } from './../managers/module' import { join, extname } from 'path' -import { ExtensionManager } from '../managers/extension' -import { WindowManager } from '../managers/window' -import { manifest, tarball } from 'pacote' import { getActiveExtensions, getAllExtensions, installExtensions, -} from '../extension/store' -import { getExtension } from '../extension/store' -import { removeExtension } from '../extension/store' -import Extension from '../extension/extension' -import { userSpacePath } from '../utils/path' +} from './../extension/store' +import { getExtension } from './../extension/store' +import { removeExtension } from './../extension/store' +import Extension from './../extension/extension' +import { getResourcePath, userSpacePath } from './../utils/path' export function handleExtensionIPCs() { /**MARK: General handlers */ @@ -48,11 +45,7 @@ export function handleExtensionIPCs() { * @returns An array of paths to the base extensions. */ ipcMain.handle('extension:baseExtensions', async (_event) => { - const baseExtensionPath = join( - __dirname, - '../', - app.isPackaged ? '../../app.asar.unpacked/pre-install' : '../pre-install' - ) + const baseExtensionPath = join(getResourcePath(), 'pre-install') return readdirSync(baseExtensionPath) .filter((file) => extname(file) === '.tgz') .map((file) => join(baseExtensionPath, file)) diff --git a/electron/handlers/fs.ts b/electron/handlers/fs.ts index 9c39c10926..16cef6eb68 100644 --- a/electron/handlers/fs.ts +++ b/electron/handlers/fs.ts @@ -1,8 +1,9 @@ -import { app, ipcMain } from 'electron' +import { ipcMain } from 'electron' import * as fs from 'fs' +import fse from 'fs-extra' import { join } from 'path' import readline from 'readline' -import { userSpacePath } from '../utils/path' +import { userSpacePath } from './../utils/path' /** * Handles file system operations. @@ -145,6 +146,12 @@ export function handleFsIPCs() { } }) + ipcMain.handle('copyFile', async (_event, src: string, dest: string) => { + console.debug(`Copying file from ${src} to ${dest}`) + + return fse.copySync(src, dest, { overwrite: false }) + }) + /** * Reads a file line by line. * @param event - The event object. diff --git a/electron/handlers/update.ts b/electron/handlers/update.ts index 340db54b9b..08d32fffeb 100644 --- a/electron/handlers/update.ts +++ b/electron/handlers/update.ts @@ -1,5 +1,5 @@ import { app, dialog } from "electron"; -import { WindowManager } from "../managers/window"; +import { WindowManager } from "./../managers/window"; import { autoUpdater } from "electron-updater"; export function handleAppUpdates() { diff --git a/electron/invokers/fs.ts b/electron/invokers/fs.ts index d7d204d0ad..309562ad6e 100644 --- a/electron/invokers/fs.ts +++ b/electron/invokers/fs.ts @@ -67,6 +67,20 @@ export function fsInvokers() { * @param {string} path - The path of the directory to remove. */ rmdir: (path: string) => ipcRenderer.invoke('rmdir', path), + + /** + * Copies a file from the source path to the destination path. + * @param {string} src - The source path of the file to copy. + * @param {string} dest - The destination path where the file should be copied. + */ + copyFile: (src: string, dest: string) => ipcRenderer.invoke('copyFile', src, dest), + + /** + * Retrieves the resource path. + * @returns {Promise} A promise that resolves to the resource path. + */ + getResourcePath: () => ipcRenderer.invoke('getResourcePath'), + } return interfaces diff --git a/electron/main.ts b/electron/main.ts index cfd6ca6651..1898368661 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,7 +1,7 @@ import { app, BrowserWindow } from 'electron' import { join } from 'path' import { setupMenu } from './utils/menu' -import { handleFsIPCs } from './handlers/fs' +import { createUserSpace, getResourcePath } from './utils/path' /** * Managers @@ -18,9 +18,11 @@ import { handleThemesIPCs } from './handlers/theme' import { handleExtensionIPCs } from './handlers/extension' import { handleAppIPCs } from './handlers/app' import { handleAppUpdates } from './handlers/update' +import { handleFsIPCs } from './handlers/fs' app .whenReady() + .then(createUserSpace) .then(ExtensionManager.instance.migrateExtensions) .then(ExtensionManager.instance.setupExtensions) .then(setupMenu) @@ -56,7 +58,7 @@ function createMainWindow() { }) const startURL = app.isPackaged - ? `file://${join(__dirname, '../renderer/index.html')}` + ? `file://${join(__dirname, '..', 'renderer', 'index.html')}` : 'http://localhost:3000' /* Load frontend app to the window */ diff --git a/electron/managers/extension.ts b/electron/managers/extension.ts index e23c75ddfb..7eef24877e 100644 --- a/electron/managers/extension.ts +++ b/electron/managers/extension.ts @@ -1,10 +1,10 @@ import { app } from 'electron' -import { init } from '../extension' +import { init } from './../extension' import { join, resolve } from 'path' import { rmdir } from 'fs' import Store from 'electron-store' import { existsSync, mkdirSync, writeFileSync } from 'fs' -import { userSpacePath } from '../utils/path' +import { userSpacePath } from './../utils/path' /** * Manages extension installation and migration. */ diff --git a/electron/managers/module.ts b/electron/managers/module.ts index 43dda0fb6e..dc16d0d227 100644 --- a/electron/managers/module.ts +++ b/electron/managers/module.ts @@ -1,4 +1,4 @@ -import { dispose } from "../utils/disposable"; +import { dispose } from "./../utils/disposable"; /** * Manages imported modules. diff --git a/electron/package.json b/electron/package.json index 46e9b328c6..627f5ad541 100644 --- a/electron/package.json +++ b/electron/package.json @@ -13,10 +13,12 @@ "renderer/**/*", "build/*.{js,map}", "build/**/*.{js,map}", - "pre-install" + "pre-install", + "models/**/*" ], "asarUnpack": [ - "pre-install" + "pre-install", + "models" ], "publish": [ { @@ -70,6 +72,7 @@ "@uiball/loaders": "^1.3.0", "electron-store": "^8.1.0", "electron-updater": "^6.1.4", + "fs-extra": "^11.2.0", "pacote": "^17.0.4", "request": "^2.88.2", "request-progress": "^3.0.0", diff --git a/electron/utils/path.ts b/electron/utils/path.ts index 30eb0ef2d0..8f30925611 100644 --- a/electron/utils/path.ts +++ b/electron/utils/path.ts @@ -1,4 +1,19 @@ import { join } from 'path' import { app } from 'electron' +import { mkdir } from 'fs-extra' + +export async function createUserSpace(): Promise { + return mkdir(userSpacePath).catch(() => {}) +} export const userSpacePath = join(app.getPath('home'), 'jan') + +export function getResourcePath() { + let appPath = join(app.getAppPath(), '..', 'app.asar.unpacked') + + if (!app.isPackaged) { + // for development mode + appPath = join(__dirname, '..', '..') + } + return appPath +} diff --git a/extensions/inference-extension/src/index.ts b/extensions/inference-extension/src/index.ts index 1ba471ab1f..e8e7758bb0 100644 --- a/extensions/inference-extension/src/index.ts +++ b/extensions/inference-extension/src/index.ts @@ -146,7 +146,6 @@ export default class JanInferenceExtension implements InferenceExtension { object: "thread.message", }; events.emit(EventName.OnMessageResponse, message); - console.log(JSON.stringify(data, null, 2)); instance.isCancelled = false; instance.controller = new AbortController(); diff --git a/extensions/model-extension/src/@types/global.d.ts b/extensions/model-extension/src/@types/global.d.ts index 87056c342c..bb030c762c 100644 --- a/extensions/model-extension/src/@types/global.d.ts +++ b/extensions/model-extension/src/@types/global.d.ts @@ -1,3 +1,2 @@ -declare const PLUGIN_NAME: string; -declare const MODULE_PATH: string; -declare const MODEL_CATALOG_URL: string; +declare const PLUGIN_NAME: string +declare const MODULE_PATH: string diff --git a/extensions/model-extension/src/@types/schema.ts b/extensions/model-extension/src/@types/schema.ts deleted file mode 100644 index 1d3c3a7d14..0000000000 --- a/extensions/model-extension/src/@types/schema.ts +++ /dev/null @@ -1,21 +0,0 @@ -interface Version { - name: string - quantMethod: string - bits: number - size: number - maxRamRequired: number - usecase: string - downloadLink: string -} -interface ModelSchema { - id: string - name: string - shortDescription: string - avatarUrl: string - longDescription: string - author: string - version: string - modelUrl: string - tags: string[] - versions: Version[] -} diff --git a/extensions/model-extension/src/helpers/modelParser.ts b/extensions/model-extension/src/helpers/modelParser.ts deleted file mode 100644 index 3a397fb7da..0000000000 --- a/extensions/model-extension/src/helpers/modelParser.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ModelCatalog } from '@janhq/core' - -export const parseToModel = (modelGroup): ModelCatalog => { - const modelVersions = [] - modelGroup.versions.forEach((v) => { - const model = { - object: 'model', - version: modelGroup.version, - source_url: v.downloadLink, - id: v.name, - name: v.name, - owned_by: 'you', - created: 0, - description: modelGroup.longDescription, - state: 'to_download', - settings: v.settings, - parameters: v.parameters, - metadata: { - engine: '', - quantization: v.quantMethod, - size: v.size, - binaries: [], - maxRamRequired: v.maxRamRequired, - author: modelGroup.author, - avatarUrl: modelGroup.avatarUrl, - }, - } - modelVersions.push(model) - }) - - const modelCatalog: ModelCatalog = { - id: modelGroup.id, - name: modelGroup.name, - avatarUrl: modelGroup.avatarUrl, - shortDescription: modelGroup.shortDescription, - longDescription: modelGroup.longDescription, - author: modelGroup.author, - version: modelGroup.version, - modelUrl: modelGroup.modelUrl, - releaseDate: modelGroup.createdAt, - tags: modelGroup.tags, - availableVersions: modelVersions, - } - - return modelCatalog -} diff --git a/extensions/model-extension/src/index.ts b/extensions/model-extension/src/index.ts index a2b0be3043..d0267b84e6 100644 --- a/extensions/model-extension/src/index.ts +++ b/extensions/model-extension/src/index.ts @@ -1,6 +1,12 @@ -import { ExtensionType, fs, downloadFile, abortDownload } from '@janhq/core' -import { ModelExtension, Model, ModelCatalog } from '@janhq/core' -import { parseToModel } from './helpers/modelParser' +import { + ExtensionType, + fs, + downloadFile, + abortDownload, + getResourcePath, + getUserSpace, +} from '@janhq/core' +import { ModelExtension, Model, ModelState } from '@janhq/core' import { join } from 'path' /** @@ -24,10 +30,7 @@ export default class JanModelExtension implements ModelExtension { * @override */ onLoad(): void { - /** Cloud Native - * TODO: Fetch all downloading progresses? - **/ - fs.mkdir(JanModelExtension._homeDir) + this.copyModelsToHomeDir() } /** @@ -36,6 +39,30 @@ export default class JanModelExtension implements ModelExtension { */ onUnload(): void {} + private async copyModelsToHomeDir() { + try { + // list all of the files under the home directory + const files = await fs.listFiles('') + + if (files.includes(JanModelExtension._homeDir)) { + // ignore if the model is already downloaded + console.debug('Model already downloaded') + return + } + + // copy models folder from resources to home directory + const resourePath = await getResourcePath() + const srcPath = join(resourePath, 'models') + + const userSpace = await getUserSpace() + const destPath = join(userSpace, JanModelExtension._homeDir) + + await fs.copyFile(srcPath, destPath) + } catch (err) { + console.error(err) + } + } + /** * Downloads a machine learning model. * @param model - The model to download. @@ -57,11 +84,11 @@ export default class JanModelExtension implements ModelExtension { * @returns {Promise} A promise that resolves when the download has been cancelled. */ async cancelModelDownload(modelId: string): Promise { - return abortDownload(join(JanModelExtension._homeDir, modelId, modelId)).then( - () => { - fs.rmdir(join(JanModelExtension._homeDir, modelId)) - } - ) + return abortDownload( + join(JanModelExtension._homeDir, modelId, modelId) + ).then(() => { + fs.deleteFile(join(JanModelExtension._homeDir, modelId, modelId)) + }) } /** @@ -72,7 +99,26 @@ export default class JanModelExtension implements ModelExtension { async deleteModel(modelId: string): Promise { try { const dirPath = join(JanModelExtension._homeDir, modelId) - await fs.rmdir(dirPath) + + // remove all files under dirPath except model.json + const files = await fs.listFiles(dirPath) + const deletePromises = files.map((fileName: string) => { + if (fileName !== JanModelExtension._modelMetadataFileName) { + return fs.deleteFile(join(dirPath, fileName)) + } + }) + await Promise.allSettled(deletePromises) + + // update the state as default + const jsonFilePath = join( + dirPath, + JanModelExtension._modelMetadataFileName + ) + const json = await fs.readFile(jsonFilePath) + const model = JSON.parse(json) as Model + delete model.state + + await fs.writeFile(jsonFilePath, JSON.stringify(model, null, 2)) } catch (err) { console.error(err) } @@ -91,7 +137,17 @@ export default class JanModelExtension implements ModelExtension { ) try { - await fs.writeFile(jsonFilePath, JSON.stringify(model, null, 2)) + await fs.writeFile( + jsonFilePath, + JSON.stringify( + { + ...model, + state: ModelState.Ready, + }, + null, + 2 + ) + ) } catch (err) { console.error(err) } @@ -102,39 +158,62 @@ export default class JanModelExtension implements ModelExtension { * @returns A Promise that resolves with an array of all models. */ async getDownloadedModels(): Promise { - const results: Model[] = [] - const allDirs: string[] = await fs.listFiles(JanModelExtension._homeDir) - for (const dir of allDirs) { - const modelDirPath = join(JanModelExtension._homeDir, dir) - const isModelDir = await fs.isDirectory(modelDirPath) - if (!isModelDir) { - // if not a directory, ignore - continue + const models = await this.getModelsMetadata() + return models.filter((model) => model.state === ModelState.Ready) + } + + private async getModelsMetadata(): Promise { + try { + const filesUnderJanRoot = await fs.listFiles('') + if (!filesUnderJanRoot.includes(JanModelExtension._homeDir)) { + console.debug('model folder not found') + return [] } - const jsonFiles: string[] = (await fs.listFiles(modelDirPath)).filter( - (fileName: string) => fileName === JanModelExtension._modelMetadataFileName - ) + const files: string[] = await fs.listFiles(JanModelExtension._homeDir) - for (const json of jsonFiles) { - const model: Model = JSON.parse( - await fs.readFile(join(modelDirPath, json)) + const allDirectories: string[] = [] + for (const file of files) { + const isDirectory = await fs.isDirectory( + join(JanModelExtension._homeDir, file) ) - results.push(model) + if (isDirectory) { + allDirectories.push(file) + } } + + const readJsonPromises = allDirectories.map((dirName) => { + const jsonPath = join( + JanModelExtension._homeDir, + dirName, + JanModelExtension._modelMetadataFileName + ) + return this.readModelMetadata(jsonPath) + }) + const results = await Promise.allSettled(readJsonPromises) + const modelData = results.map((result) => { + if (result.status === 'fulfilled') { + return JSON.parse(result.value) as Model + } else { + console.error(result.reason) + } + }) + return modelData + } catch (err) { + console.error(err) + return [] } + } - return results + private readModelMetadata(path: string) { + return fs.readFile(join(path)) } /** * Gets all available models. * @returns A Promise that resolves with an array of all models. */ - getConfiguredModels(): Promise { - // Add a timestamp to the URL to prevent caching - return import( - /* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}` - ).then((module) => module.default.map((e) => parseToModel(e))) + async getConfiguredModels(): Promise { + return this.getModelsMetadata() } } diff --git a/extensions/model-extension/webpack.config.js b/extensions/model-extension/webpack.config.js index 3475516ed0..a9332da997 100644 --- a/extensions/model-extension/webpack.config.js +++ b/extensions/model-extension/webpack.config.js @@ -19,9 +19,6 @@ module.exports = { new webpack.DefinePlugin({ PLUGIN_NAME: JSON.stringify(packageJson.name), MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`), - MODEL_CATALOG_URL: JSON.stringify( - 'https://cdn.jsdelivr.net/npm/@janhq/models@latest/dist/index.js' - ), }), ], output: { diff --git a/package.json b/package.json index a2476887f2..9192a02382 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,7 @@ "uikit", "core", "electron", - "web", - "server" + "web" ], "nohoist": [ "uikit", @@ -17,15 +16,13 @@ "electron", "electron/**", "web", - "web/**", - "server", - "server/**" + "web/**" ] }, "scripts": { "lint": "yarn workspace jan lint && yarn workspace jan-web lint", "test": "yarn workspace jan test:e2e", - "dev:electron": "yarn workspace jan dev", + "dev:electron": "cpx \"models/**\" \"electron/models/\" && yarn workspace jan dev", "dev:web": "yarn workspace jan-web dev", "dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"", "test-local": "yarn lint && yarn build:test && yarn test", @@ -33,7 +30,7 @@ "build:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build", "build:core": "cd core && yarn install && yarn run build", "build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"", - "build:electron": "yarn workspace jan build", + "build:electron": "yarn workspace jan build && cpx \"models/**\" \"electron/models/\"", "build:electron:test": "yarn workspace jan build:test", "build:extensions": "rimraf ./electron/pre-install/*.tgz && concurrently --kill-others-on-fail \"cd ./extensions/conversational-extension && npm install && npm run build:publish\" \"cd ./extensions/inference-extension && npm install && npm run build:publish\" \"cd ./extensions/model-extension && npm install && npm run build:publish\" \"cd ./extensions/monitoring-extension && npm install && npm run build:publish\" \"cd ./extensions/assistant-extension && npm install && npm run build:publish\"", "build:test": "yarn build:web && yarn workspace jan build:test", diff --git a/web/containers/ItemCardSidebar/index.tsx b/web/containers/ItemCardSidebar/index.tsx index b6a7bacbd9..627d7f45dc 100644 --- a/web/containers/ItemCardSidebar/index.tsx +++ b/web/containers/ItemCardSidebar/index.tsx @@ -1,9 +1,16 @@ type Props = { title: string description?: string + disabled?: boolean + onChange?: (text?: string) => void } -export default function ItemCardSidebar({ description, title }: Props) { +export default function ItemCardSidebar({ + description, + title, + disabled, + onChange, +}: Props) { return (
@@ -11,9 +18,11 @@ export default function ItemCardSidebar({ description, title }: Props) {
onChange?.(e.target.value)} />
) diff --git a/web/containers/Layout/BottomBar/DownloadingState/index.tsx b/web/containers/Layout/BottomBar/DownloadingState/index.tsx index bc456bf80c..1aad0fb1c8 100644 --- a/web/containers/Layout/BottomBar/DownloadingState/index.tsx +++ b/web/containers/Layout/BottomBar/DownloadingState/index.tsx @@ -69,18 +69,14 @@ export default function DownloadingState() { />
-

{item?.fileName}

+

{item?.modelId}

{formatDownloadPercentage(item?.percent)}
+ {activeThread && ( + + )} ) } diff --git a/web/containers/ModalCancelDownload/index.tsx b/web/containers/ModalCancelDownload/index.tsx index de54e1cf8c..8619c543c0 100644 --- a/web/containers/ModalCancelDownload/index.tsx +++ b/web/containers/ModalCancelDownload/index.tsx @@ -24,34 +24,30 @@ import { extensionManager } from '@/extension' import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom' type Props = { - suitableModel: Model + model: Model isFromList?: boolean } -export default function ModalCancelDownload({ - suitableModel, - isFromList, -}: Props) { +export default function ModalCancelDownload({ model, isFromList }: Props) { const { modelDownloadStateAtom } = useDownloadState() const downloadAtom = useMemo( - () => atom((get) => get(modelDownloadStateAtom)[suitableModel.name]), + () => atom((get) => get(modelDownloadStateAtom)[model.id]), // eslint-disable-next-line react-hooks/exhaustive-deps - [suitableModel.name] + [model.id] ) const models = useAtomValue(downloadingModelsAtom) const downloadState = useAtomValue(downloadAtom) + const cancelText = `Cancel ${formatDownloadPercentage(downloadState.percent)}` return ( {isFromList ? ( ) : ( - + )} @@ -60,7 +56,7 @@ export default function ModalCancelDownload({

Are you sure you want to cancel the download of  - {downloadState?.fileName}? + {downloadState?.modelId}?

@@ -71,11 +67,7 @@ export default function ModalCancelDownload({ - - {show && ( - - )} -
- )}
) diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index c845c5a45d..b51ec164cd 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useMemo, useState } from 'react' -import { Model, ModelCatalog } from '@janhq/core' +import { Model } from '@janhq/core' import { Badge, Button } from '@janhq/uikit' import { atom, useAtomValue } from 'jotai' @@ -15,67 +15,41 @@ import { ModelPerformance, TagType } from '@/constants/tagType' import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels' -import useGetPerformanceTag from '@/hooks/useGetPerformanceTag' import { useMainViewState } from '@/hooks/useMainViewState' import { toGigabytes } from '@/utils/converter' -import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom' - type Props = { - suitableModel: Model - exploreModel: ModelCatalog + model: Model } -const ExploreModelItemHeader: React.FC = ({ - suitableModel, - exploreModel, -}) => { +const ExploreModelItemHeader: React.FC = ({ model }) => { const { downloadModel } = useDownloadModel() const { downloadedModels } = useGetDownloadedModels() const { modelDownloadStateAtom, downloadStates } = useDownloadState() - const { getPerformanceForModel } = useGetPerformanceTag() const [title, setTitle] = useState('Recommended') - const totalRam = useAtomValue(totalRamAtom) + const [performanceTag, setPerformanceTag] = useState( ModelPerformance.PerformancePositive ) const downloadAtom = useMemo( - () => atom((get) => get(modelDownloadStateAtom)[suitableModel.name]), - [suitableModel.name] + () => atom((get) => get(modelDownloadStateAtom)[model.id]), + [model.id] ) const downloadState = useAtomValue(downloadAtom) const { setMainViewState } = useMainViewState() - const calculatePerformance = useCallback( - (suitableModel: Model) => async () => { - const { title, performanceTag } = await getPerformanceForModel( - suitableModel, - totalRam - ) - setPerformanceTag(performanceTag) - setTitle(title) - }, - [totalRam] - ) - - useEffect(() => { - calculatePerformance(suitableModel) - }, [suitableModel]) - const onDownloadClick = useCallback(() => { - downloadModel(suitableModel) + downloadModel(model) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [suitableModel]) + }, [model]) - // TODO: Comparing between Model Id and Version Name? - const isDownloaded = - downloadedModels.find((model) => model.id === suitableModel.name) != null + const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null let downloadButton = ( ) @@ -93,7 +67,7 @@ const ExploreModelItemHeader: React.FC = ({ } if (downloadState != null && downloadStates.length > 0) { - downloadButton = + downloadButton = } const renderBadge = (performance: TagType) => { @@ -115,7 +89,7 @@ const ExploreModelItemHeader: React.FC = ({ return (
- {exploreModel.name} + {model.name} {performanceTag && renderBadge(performanceTag)}
{downloadButton} diff --git a/web/screens/ExploreModels/ExploreModelList/index.tsx b/web/screens/ExploreModels/ExploreModelList/index.tsx index 8c0c9bdb4f..eea9f0238c 100644 --- a/web/screens/ExploreModels/ExploreModelList/index.tsx +++ b/web/screens/ExploreModels/ExploreModelList/index.tsx @@ -1,16 +1,14 @@ -import { ModelCatalog } from '@janhq/core' +import { Model } from '@janhq/core' import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem' type Props = { - models: ModelCatalog[] + models: Model[] } const ExploreModelList: React.FC = ({ models }) => (
- {models?.map((item, i) => ( - - ))} + {models?.map((model) => )}
) diff --git a/web/screens/ExploreModels/ModelVersionItem/index.tsx b/web/screens/ExploreModels/ModelVersionItem/index.tsx index f7d09307b0..e16c477f6e 100644 --- a/web/screens/ExploreModels/ModelVersionItem/index.tsx +++ b/web/screens/ExploreModels/ModelVersionItem/index.tsx @@ -2,7 +2,7 @@ import React, { useMemo } from 'react' import { Model } from '@janhq/core' -import { Badge, Button } from '@janhq/uikit' +import { Button } from '@janhq/uikit' import { atom, useAtomValue } from 'jotai' import ModalCancelDownload from '@/containers/ModalCancelDownload' @@ -63,7 +63,7 @@ const ModelVersionItem: React.FC = ({ model }) => { } if (downloadState != null && downloadStates.length > 0) { - downloadButton = + downloadButton = } return ( @@ -74,16 +74,7 @@ const ModelVersionItem: React.FC = ({ model }) => {
-
- {`${toGigabytes( - model.metadata.maxRamRequired - )} RAM required`} - {toGigabytes(model.metadata.size)} -
+
{downloadButton}
diff --git a/web/screens/MyModels/BlankState/index.tsx b/web/screens/MyModels/BlankState/index.tsx index a820440d03..c0d7be6bb5 100644 --- a/web/screens/MyModels/BlankState/index.tsx +++ b/web/screens/MyModels/BlankState/index.tsx @@ -55,7 +55,7 @@ export default function BlankStateMyModel() { } />
-

{item?.fileName}

+

{item?.modelId}

{formatDownloadPercentage(item?.percent)}
diff --git a/web/screens/MyModels/index.tsx b/web/screens/MyModels/index.tsx index d9c2d2880a..c8176f010d 100644 --- a/web/screens/MyModels/index.tsx +++ b/web/screens/MyModels/index.tsx @@ -63,10 +63,7 @@ const MyModelsScreen = () => {
- + {model.metadata.author.charAt(0)} diff --git a/web/screens/Settings/index.tsx b/web/screens/Settings/index.tsx index ced7589b58..63c343add6 100644 --- a/web/screens/Settings/index.tsx +++ b/web/screens/Settings/index.tsx @@ -30,7 +30,6 @@ const SettingsScreen = () => { setMenus(menu) }, []) - const preferenceExtensions = preferenceItems .map((x) => x.extensionnName) .filter((x, i) => { diff --git a/web/types/downloadState.d.ts b/web/types/downloadState.d.ts index cb154522db..3c3389b4fa 100644 --- a/web/types/downloadState.d.ts +++ b/web/types/downloadState.d.ts @@ -4,7 +4,6 @@ type DownloadState = { speed: number percent: number size: DownloadSize - fileName: string error?: string } diff --git a/web/utils/dummy.ts b/web/utils/dummy.ts deleted file mode 100644 index bde61e38fe..0000000000 --- a/web/utils/dummy.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { ModelCatalog, ModelState } from '@janhq/core' - -export const dummyModel: ModelCatalog = { - id: 'aladar/TinyLLama-v0-GGUF', - name: 'TinyLLama-v0-GGUF', - shortDescription: 'TinyLlama-1.1B-Chat-v0.3-GGUF', - longDescription: 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/tree/main', - avatarUrl: '', - releaseDate: Date.now(), - author: 'aladar', - version: '1.0.0', - modelUrl: 'aladar/TinyLLama-v0-GGUF', - tags: ['freeform', 'tags'], - availableVersions: [ - { - object: 'model', - version: '1.0.0', - source_url: - 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/resolve/main/TinyLLama-v0.Q8_0.gguf', - id: 'TinyLLama-v0.Q8_0.gguf', - name: 'TinyLLama-v0.Q8_0.gguf', - owned_by: 'you', - created: 0, - description: '', - state: ModelState.ToDownload, - settings: { - ctx_len: 2048, - ngl: 100, - embedding: true, - n_parallel: 4, - }, - parameters: { - temperature: 0.7, - token_limit: 2048, - top_k: 0, - top_p: 1, - stream: true, - }, - metadata: { - engine: '', - quantization: '', - size: 5816320, - binaries: [], - maxRamRequired: 256000000, - author: 'aladar', - avatarUrl: '', - }, - }, - { - object: 'model', - version: '1.0.0', - source_url: - 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/resolve/main/TinyLLama-v0.f16.gguf', - id: 'TinyLLama-v0.f16.gguf', - name: 'TinyLLama-v0.f16.gguf', - owned_by: 'you', - created: 0, - description: '', - state: ModelState.ToDownload, - settings: { - ctx_len: 2048, - ngl: 100, - embedding: true, - n_parallel: 4, - }, - parameters: { - temperature: 0.7, - token_limit: 2048, - top_k: 0, - top_p: 1, - stream: true, - }, - metadata: { - engine: '', - quantization: '', - size: 5816320, - binaries: [], - maxRamRequired: 256000000, - author: 'aladar', - avatarUrl: '', - }, - }, - { - object: 'model', - version: '1.0.0', - source_url: - 'https://huggingface.co/aladar/TinyLLama-v0-GGUF/resolve/main/TinyLLama-v0.f32.gguf', - id: 'TinyLLama-v0.f32.gguf', - name: 'TinyLLama-v0.f32.gguf', - owned_by: 'you', - created: 0, - description: '', - state: ModelState.ToDownload, - settings: { - ctx_len: 2048, - ngl: 100, - embedding: true, - n_parallel: 4, - }, - parameters: { - temperature: 0.7, - token_limit: 2048, - top_k: 0, - top_p: 1, - stream: true, - }, - metadata: { - engine: '', - quantization: '', - size: 5816320, - binaries: [], - maxRamRequired: 256000000, - author: 'aladar', - avatarUrl: '', - }, - }, - ], -}