feat: pre-populate Jan's /models folder (#796)
Signed-off-by: James <james@jan.ai> Co-authored-by: James <james@jan.ai>
This commit is contained in:
parent
e6812b1247
commit
1bf4c1b621
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ dist
|
|||||||
build
|
build
|
||||||
.DS_Store
|
.DS_Store
|
||||||
electron/renderer
|
electron/renderer
|
||||||
|
electron/models
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@ -54,6 +54,9 @@ const getUserSpace = (): Promise<string> => window.core.api?.getUserSpace();
|
|||||||
const openFileExplorer: (path: string) => Promise<any> = (path) =>
|
const openFileExplorer: (path: string) => Promise<any> = (path) =>
|
||||||
window.core.api?.openFileExplorer(path);
|
window.core.api?.openFileExplorer(path);
|
||||||
|
|
||||||
|
const getResourcePath: () => Promise<string> = () =>
|
||||||
|
window.core.api?.getResourcePath();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register extension point function type definition
|
* Register extension point function type definition
|
||||||
*/
|
*/
|
||||||
@ -74,4 +77,5 @@ export {
|
|||||||
appDataPath,
|
appDataPath,
|
||||||
getUserSpace,
|
getUserSpace,
|
||||||
openFileExplorer,
|
openFileExplorer,
|
||||||
|
getResourcePath,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { BaseExtension } from "../extension";
|
import { BaseExtension } from "../extension";
|
||||||
import { Model, ModelCatalog } from "../types/index";
|
import { Model } from "../types/index";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model extension for managing models.
|
* Model extension for managing models.
|
||||||
@ -43,5 +43,5 @@ export abstract class ModelExtension extends BaseExtension {
|
|||||||
* Gets a list of configured models.
|
* Gets a list of configured models.
|
||||||
* @returns A Promise that resolves with an array of configured models.
|
* @returns A Promise that resolves with an array of configured models.
|
||||||
*/
|
*/
|
||||||
abstract getConfiguredModels(): Promise<ModelCatalog[]>;
|
abstract getConfiguredModels(): Promise<Model[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,6 +62,9 @@ const deleteFile: (path: string) => Promise<any> = (path) =>
|
|||||||
const appendFile: (path: string, data: string) => Promise<any> = (path, data) =>
|
const appendFile: (path: string, data: string) => Promise<any> = (path, data) =>
|
||||||
window.core.api?.appendFile(path, data);
|
window.core.api?.appendFile(path, data);
|
||||||
|
|
||||||
|
const copyFile: (src: string, dest: string) => Promise<any> = (src, dest) =>
|
||||||
|
window.core.api?.copyFile(src, dest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a file line by line.
|
* Reads a file line by line.
|
||||||
* @param {string} path - The path of the file to read.
|
* @param {string} path - The path of the file to read.
|
||||||
@ -80,4 +83,5 @@ export const fs = {
|
|||||||
deleteFile,
|
deleteFile,
|
||||||
appendFile,
|
appendFile,
|
||||||
readLineByLine,
|
readLineByLine,
|
||||||
|
copyFile,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -180,7 +180,7 @@ export interface Model {
|
|||||||
/**
|
/**
|
||||||
* The version of the model.
|
* The version of the model.
|
||||||
*/
|
*/
|
||||||
version: string;
|
version: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The model download source. It can be an external url or a local filepath.
|
* The model download source. It can be an external url or a local filepath.
|
||||||
@ -197,12 +197,6 @@ export interface Model {
|
|||||||
*/
|
*/
|
||||||
name: string;
|
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
|
* The Unix timestamp (in seconds) for when the model was created
|
||||||
*/
|
*/
|
||||||
@ -236,11 +230,16 @@ export interface Model {
|
|||||||
metadata: ModelMetadata;
|
metadata: ModelMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelMetadata = {
|
||||||
|
author: string;
|
||||||
|
tags: string[];
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Model transition states.
|
* The Model transition states.
|
||||||
*/
|
*/
|
||||||
export enum ModelState {
|
export enum ModelState {
|
||||||
ToDownload = "to_download",
|
|
||||||
Downloading = "downloading",
|
Downloading = "downloading",
|
||||||
Ready = "ready",
|
Ready = "ready",
|
||||||
Running = "running",
|
Running = "running",
|
||||||
@ -250,65 +249,27 @@ export enum ModelState {
|
|||||||
* The available model settings.
|
* The available model settings.
|
||||||
*/
|
*/
|
||||||
export type ModelSettingParams = {
|
export type ModelSettingParams = {
|
||||||
ctx_len: number;
|
ctx_len?: number;
|
||||||
ngl: number;
|
ngl?: number;
|
||||||
embedding: boolean;
|
embedding?: boolean;
|
||||||
n_parallel: number;
|
n_parallel?: number;
|
||||||
|
system_prompt?: string;
|
||||||
|
user_prompt?: string;
|
||||||
|
ai_prompt?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The available model runtime parameters.
|
* The available model runtime parameters.
|
||||||
*/
|
*/
|
||||||
export type ModelRuntimeParam = {
|
export type ModelRuntimeParam = {
|
||||||
temperature: number;
|
temperature?: number;
|
||||||
token_limit: number;
|
token_limit?: number;
|
||||||
top_k: number;
|
top_k?: number;
|
||||||
top_p: number;
|
top_p?: number;
|
||||||
stream: boolean;
|
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.
|
* Assistant type defines the shape of an assistant object.
|
||||||
* @stored
|
* @stored
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { app, ipcMain, shell } from 'electron'
|
import { app, ipcMain, shell } from 'electron'
|
||||||
import { ModuleManager } from '../managers/module'
|
import { ModuleManager } from './../managers/module'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { ExtensionManager } from '../managers/extension'
|
import { ExtensionManager } from './../managers/extension'
|
||||||
import { WindowManager } from '../managers/window'
|
import { WindowManager } from './../managers/window'
|
||||||
import { userSpacePath } from '../utils/path'
|
import { userSpacePath } from './../utils/path'
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
export function handleAppIPCs() {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { app, ipcMain } from 'electron'
|
import { app, ipcMain } from 'electron'
|
||||||
import { DownloadManager } from '../managers/download'
|
import { DownloadManager } from './../managers/download'
|
||||||
import { resolve, join } from 'path'
|
import { resolve, join } from 'path'
|
||||||
import { WindowManager } from '../managers/window'
|
import { WindowManager } from './../managers/window'
|
||||||
import request from 'request'
|
import request from 'request'
|
||||||
import { createWriteStream, unlink } from 'fs'
|
import { createWriteStream } from 'fs'
|
||||||
|
import { getResourcePath } from './../utils/path'
|
||||||
const progress = require('request-progress')
|
const progress = require('request-progress')
|
||||||
|
|
||||||
export function handleDownloaderIPCs() {
|
export function handleDownloaderIPCs() {
|
||||||
@ -37,6 +38,10 @@ export function handleDownloaderIPCs() {
|
|||||||
rq?.abort()
|
rq?.abort()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('getResourcePath', async (_event) => {
|
||||||
|
return getResourcePath()
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads a file from a given URL.
|
* Downloads a file from a given URL.
|
||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
import { app, ipcMain, webContents } from 'electron'
|
import { ipcMain, webContents } from 'electron'
|
||||||
import { readdirSync, rmdir, writeFileSync } from 'fs'
|
import { readdirSync } from 'fs'
|
||||||
import { ModuleManager } from '../managers/module'
|
import { ModuleManager } from './../managers/module'
|
||||||
import { join, extname } from 'path'
|
import { join, extname } from 'path'
|
||||||
import { ExtensionManager } from '../managers/extension'
|
|
||||||
import { WindowManager } from '../managers/window'
|
|
||||||
import { manifest, tarball } from 'pacote'
|
|
||||||
import {
|
import {
|
||||||
getActiveExtensions,
|
getActiveExtensions,
|
||||||
getAllExtensions,
|
getAllExtensions,
|
||||||
installExtensions,
|
installExtensions,
|
||||||
} from '../extension/store'
|
} from './../extension/store'
|
||||||
import { getExtension } from '../extension/store'
|
import { getExtension } from './../extension/store'
|
||||||
import { removeExtension } from '../extension/store'
|
import { removeExtension } from './../extension/store'
|
||||||
import Extension from '../extension/extension'
|
import Extension from './../extension/extension'
|
||||||
import { userSpacePath } from '../utils/path'
|
import { getResourcePath, userSpacePath } from './../utils/path'
|
||||||
|
|
||||||
export function handleExtensionIPCs() {
|
export function handleExtensionIPCs() {
|
||||||
/**MARK: General handlers */
|
/**MARK: General handlers */
|
||||||
@ -48,11 +45,7 @@ export function handleExtensionIPCs() {
|
|||||||
* @returns An array of paths to the base extensions.
|
* @returns An array of paths to the base extensions.
|
||||||
*/
|
*/
|
||||||
ipcMain.handle('extension:baseExtensions', async (_event) => {
|
ipcMain.handle('extension:baseExtensions', async (_event) => {
|
||||||
const baseExtensionPath = join(
|
const baseExtensionPath = join(getResourcePath(), 'pre-install')
|
||||||
__dirname,
|
|
||||||
'../',
|
|
||||||
app.isPackaged ? '../../app.asar.unpacked/pre-install' : '../pre-install'
|
|
||||||
)
|
|
||||||
return readdirSync(baseExtensionPath)
|
return readdirSync(baseExtensionPath)
|
||||||
.filter((file) => extname(file) === '.tgz')
|
.filter((file) => extname(file) === '.tgz')
|
||||||
.map((file) => join(baseExtensionPath, file))
|
.map((file) => join(baseExtensionPath, file))
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { app, ipcMain } from 'electron'
|
import { ipcMain } from 'electron'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
|
import fse from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import readline from 'readline'
|
import readline from 'readline'
|
||||||
import { userSpacePath } from '../utils/path'
|
import { userSpacePath } from './../utils/path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles file system operations.
|
* 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.
|
* Reads a file line by line.
|
||||||
* @param event - The event object.
|
* @param event - The event object.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { app, dialog } from "electron";
|
import { app, dialog } from "electron";
|
||||||
import { WindowManager } from "../managers/window";
|
import { WindowManager } from "./../managers/window";
|
||||||
import { autoUpdater } from "electron-updater";
|
import { autoUpdater } from "electron-updater";
|
||||||
|
|
||||||
export function handleAppUpdates() {
|
export function handleAppUpdates() {
|
||||||
|
|||||||
@ -67,6 +67,20 @@ export function fsInvokers() {
|
|||||||
* @param {string} path - The path of the directory to remove.
|
* @param {string} path - The path of the directory to remove.
|
||||||
*/
|
*/
|
||||||
rmdir: (path: string) => ipcRenderer.invoke('rmdir', path),
|
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<string>} A promise that resolves to the resource path.
|
||||||
|
*/
|
||||||
|
getResourcePath: () => ipcRenderer.invoke('getResourcePath'),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces
|
return interfaces
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { app, BrowserWindow } from 'electron'
|
import { app, BrowserWindow } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { setupMenu } from './utils/menu'
|
import { setupMenu } from './utils/menu'
|
||||||
import { handleFsIPCs } from './handlers/fs'
|
import { createUserSpace, getResourcePath } from './utils/path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Managers
|
* Managers
|
||||||
@ -18,9 +18,11 @@ import { handleThemesIPCs } from './handlers/theme'
|
|||||||
import { handleExtensionIPCs } from './handlers/extension'
|
import { handleExtensionIPCs } from './handlers/extension'
|
||||||
import { handleAppIPCs } from './handlers/app'
|
import { handleAppIPCs } from './handlers/app'
|
||||||
import { handleAppUpdates } from './handlers/update'
|
import { handleAppUpdates } from './handlers/update'
|
||||||
|
import { handleFsIPCs } from './handlers/fs'
|
||||||
|
|
||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
|
.then(createUserSpace)
|
||||||
.then(ExtensionManager.instance.migrateExtensions)
|
.then(ExtensionManager.instance.migrateExtensions)
|
||||||
.then(ExtensionManager.instance.setupExtensions)
|
.then(ExtensionManager.instance.setupExtensions)
|
||||||
.then(setupMenu)
|
.then(setupMenu)
|
||||||
@ -56,7 +58,7 @@ function createMainWindow() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const startURL = app.isPackaged
|
const startURL = app.isPackaged
|
||||||
? `file://${join(__dirname, '../renderer/index.html')}`
|
? `file://${join(__dirname, '..', 'renderer', 'index.html')}`
|
||||||
: 'http://localhost:3000'
|
: 'http://localhost:3000'
|
||||||
|
|
||||||
/* Load frontend app to the window */
|
/* Load frontend app to the window */
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import { init } from '../extension'
|
import { init } from './../extension'
|
||||||
import { join, resolve } from 'path'
|
import { join, resolve } from 'path'
|
||||||
import { rmdir } from 'fs'
|
import { rmdir } from 'fs'
|
||||||
import Store from 'electron-store'
|
import Store from 'electron-store'
|
||||||
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
||||||
import { userSpacePath } from '../utils/path'
|
import { userSpacePath } from './../utils/path'
|
||||||
/**
|
/**
|
||||||
* Manages extension installation and migration.
|
* Manages extension installation and migration.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { dispose } from "../utils/disposable";
|
import { dispose } from "./../utils/disposable";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages imported modules.
|
* Manages imported modules.
|
||||||
|
|||||||
@ -13,10 +13,12 @@
|
|||||||
"renderer/**/*",
|
"renderer/**/*",
|
||||||
"build/*.{js,map}",
|
"build/*.{js,map}",
|
||||||
"build/**/*.{js,map}",
|
"build/**/*.{js,map}",
|
||||||
"pre-install"
|
"pre-install",
|
||||||
|
"models/**/*"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"pre-install"
|
"pre-install",
|
||||||
|
"models"
|
||||||
],
|
],
|
||||||
"publish": [
|
"publish": [
|
||||||
{
|
{
|
||||||
@ -70,6 +72,7 @@
|
|||||||
"@uiball/loaders": "^1.3.0",
|
"@uiball/loaders": "^1.3.0",
|
||||||
"electron-store": "^8.1.0",
|
"electron-store": "^8.1.0",
|
||||||
"electron-updater": "^6.1.4",
|
"electron-updater": "^6.1.4",
|
||||||
|
"fs-extra": "^11.2.0",
|
||||||
"pacote": "^17.0.4",
|
"pacote": "^17.0.4",
|
||||||
"request": "^2.88.2",
|
"request": "^2.88.2",
|
||||||
"request-progress": "^3.0.0",
|
"request-progress": "^3.0.0",
|
||||||
|
|||||||
@ -1,4 +1,19 @@
|
|||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
import { mkdir } from 'fs-extra'
|
||||||
|
|
||||||
|
export async function createUserSpace(): Promise<void> {
|
||||||
|
return mkdir(userSpacePath).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
export const userSpacePath = join(app.getPath('home'), 'jan')
|
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
|
||||||
|
}
|
||||||
|
|||||||
@ -146,7 +146,6 @@ export default class JanInferenceExtension implements InferenceExtension {
|
|||||||
object: "thread.message",
|
object: "thread.message",
|
||||||
};
|
};
|
||||||
events.emit(EventName.OnMessageResponse, message);
|
events.emit(EventName.OnMessageResponse, message);
|
||||||
console.log(JSON.stringify(data, null, 2));
|
|
||||||
|
|
||||||
instance.isCancelled = false;
|
instance.isCancelled = false;
|
||||||
instance.controller = new AbortController();
|
instance.controller = new AbortController();
|
||||||
|
|||||||
@ -1,3 +1,2 @@
|
|||||||
declare const PLUGIN_NAME: string;
|
declare const PLUGIN_NAME: string
|
||||||
declare const MODULE_PATH: string;
|
declare const MODULE_PATH: string
|
||||||
declare const MODEL_CATALOG_URL: string;
|
|
||||||
|
|||||||
@ -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[]
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -1,6 +1,12 @@
|
|||||||
import { ExtensionType, fs, downloadFile, abortDownload } from '@janhq/core'
|
import {
|
||||||
import { ModelExtension, Model, ModelCatalog } from '@janhq/core'
|
ExtensionType,
|
||||||
import { parseToModel } from './helpers/modelParser'
|
fs,
|
||||||
|
downloadFile,
|
||||||
|
abortDownload,
|
||||||
|
getResourcePath,
|
||||||
|
getUserSpace,
|
||||||
|
} from '@janhq/core'
|
||||||
|
import { ModelExtension, Model, ModelState } from '@janhq/core'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,10 +30,7 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
onLoad(): void {
|
onLoad(): void {
|
||||||
/** Cloud Native
|
this.copyModelsToHomeDir()
|
||||||
* TODO: Fetch all downloading progresses?
|
|
||||||
**/
|
|
||||||
fs.mkdir(JanModelExtension._homeDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +39,30 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
*/
|
*/
|
||||||
onUnload(): void {}
|
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.
|
* Downloads a machine learning model.
|
||||||
* @param model - The model to download.
|
* @param model - The model to download.
|
||||||
@ -57,11 +84,11 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
* @returns {Promise<void>} A promise that resolves when the download has been cancelled.
|
* @returns {Promise<void>} A promise that resolves when the download has been cancelled.
|
||||||
*/
|
*/
|
||||||
async cancelModelDownload(modelId: string): Promise<void> {
|
async cancelModelDownload(modelId: string): Promise<void> {
|
||||||
return abortDownload(join(JanModelExtension._homeDir, modelId, modelId)).then(
|
return abortDownload(
|
||||||
() => {
|
join(JanModelExtension._homeDir, modelId, modelId)
|
||||||
fs.rmdir(join(JanModelExtension._homeDir, modelId))
|
).then(() => {
|
||||||
}
|
fs.deleteFile(join(JanModelExtension._homeDir, modelId, modelId))
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,7 +99,26 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
async deleteModel(modelId: string): Promise<void> {
|
async deleteModel(modelId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const dirPath = join(JanModelExtension._homeDir, modelId)
|
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) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
}
|
}
|
||||||
@ -91,7 +137,17 @@ export default class JanModelExtension implements ModelExtension {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.writeFile(jsonFilePath, JSON.stringify(model, null, 2))
|
await fs.writeFile(
|
||||||
|
jsonFilePath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
...model,
|
||||||
|
state: ModelState.Ready,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(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.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
*/
|
*/
|
||||||
async getDownloadedModels(): Promise<Model[]> {
|
async getDownloadedModels(): Promise<Model[]> {
|
||||||
const results: Model[] = []
|
const models = await this.getModelsMetadata()
|
||||||
const allDirs: string[] = await fs.listFiles(JanModelExtension._homeDir)
|
return models.filter((model) => model.state === ModelState.Ready)
|
||||||
for (const dir of allDirs) {
|
}
|
||||||
const modelDirPath = join(JanModelExtension._homeDir, dir)
|
|
||||||
const isModelDir = await fs.isDirectory(modelDirPath)
|
private async getModelsMetadata(): Promise<Model[]> {
|
||||||
if (!isModelDir) {
|
try {
|
||||||
// if not a directory, ignore
|
const filesUnderJanRoot = await fs.listFiles('')
|
||||||
continue
|
if (!filesUnderJanRoot.includes(JanModelExtension._homeDir)) {
|
||||||
|
console.debug('model folder not found')
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonFiles: string[] = (await fs.listFiles(modelDirPath)).filter(
|
const files: string[] = await fs.listFiles(JanModelExtension._homeDir)
|
||||||
(fileName: string) => fileName === JanModelExtension._modelMetadataFileName
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const json of jsonFiles) {
|
const allDirectories: string[] = []
|
||||||
const model: Model = JSON.parse(
|
for (const file of files) {
|
||||||
await fs.readFile(join(modelDirPath, json))
|
const isDirectory = await fs.isDirectory(
|
||||||
|
join(JanModelExtension._homeDir, file)
|
||||||
)
|
)
|
||||||
results.push(model)
|
if (isDirectory) {
|
||||||
|
allDirectories.push(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
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 []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readModelMetadata(path: string) {
|
||||||
|
return fs.readFile(join(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets all available models.
|
* Gets all available models.
|
||||||
* @returns A Promise that resolves with an array of all models.
|
* @returns A Promise that resolves with an array of all models.
|
||||||
*/
|
*/
|
||||||
getConfiguredModels(): Promise<ModelCatalog[]> {
|
async getConfiguredModels(): Promise<Model[]> {
|
||||||
// Add a timestamp to the URL to prevent caching
|
return this.getModelsMetadata()
|
||||||
return import(
|
|
||||||
/* webpackIgnore: true */ MODEL_CATALOG_URL + `?t=${Date.now()}`
|
|
||||||
).then((module) => module.default.map((e) => parseToModel(e)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,9 +19,6 @@ module.exports = {
|
|||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
PLUGIN_NAME: JSON.stringify(packageJson.name),
|
||||||
MODULE_PATH: JSON.stringify(`${packageJson.name}/${packageJson.module}`),
|
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: {
|
output: {
|
||||||
|
|||||||
11
package.json
11
package.json
@ -6,8 +6,7 @@
|
|||||||
"uikit",
|
"uikit",
|
||||||
"core",
|
"core",
|
||||||
"electron",
|
"electron",
|
||||||
"web",
|
"web"
|
||||||
"server"
|
|
||||||
],
|
],
|
||||||
"nohoist": [
|
"nohoist": [
|
||||||
"uikit",
|
"uikit",
|
||||||
@ -17,15 +16,13 @@
|
|||||||
"electron",
|
"electron",
|
||||||
"electron/**",
|
"electron/**",
|
||||||
"web",
|
"web",
|
||||||
"web/**",
|
"web/**"
|
||||||
"server",
|
|
||||||
"server/**"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
"lint": "yarn workspace jan lint && yarn workspace jan-web lint",
|
||||||
"test": "yarn workspace jan test:e2e",
|
"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:web": "yarn workspace jan-web dev",
|
||||||
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
"dev": "concurrently --kill-others \"yarn dev:web\" \"wait-on http://localhost:3000 && yarn dev:electron\"",
|
||||||
"test-local": "yarn lint && yarn build:test && yarn test",
|
"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:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build",
|
||||||
"build:core": "cd core && yarn install && yarn run build",
|
"build:core": "cd core && yarn install && yarn run build",
|
||||||
"build:web": "yarn workspace jan-web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
"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: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: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",
|
"build:test": "yarn build:web && yarn workspace jan build:test",
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
type Props = {
|
type Props = {
|
||||||
title: string
|
title: string
|
||||||
description?: 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 (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -11,9 +18,11 @@ export default function ItemCardSidebar({ description, title }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
value={description}
|
value={description}
|
||||||
|
disabled={disabled}
|
||||||
type="text"
|
type="text"
|
||||||
className="block w-full rounded-md border-0 px-1 py-1.5 text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
className="block w-full rounded-md border-0 px-1 py-1.5 text-white shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
|
onChange={(e) => onChange?.(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -69,18 +69,14 @@ export default function DownloadingState() {
|
|||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between gap-x-2">
|
<div className="flex items-center justify-between gap-x-2">
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
<p className="line-clamp-1">{item?.fileName}</p>
|
<p className="line-clamp-1">{item?.modelId}</p>
|
||||||
<span>{formatDownloadPercentage(item?.percent)}</span>
|
<span>{formatDownloadPercentage(item?.percent)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
themes="outline"
|
themes="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (item?.fileName) {
|
if (item?.modelId) {
|
||||||
const model = models.find(
|
|
||||||
(e) => e.id === item?.fileName
|
|
||||||
)
|
|
||||||
if (!model) return
|
|
||||||
extensionManager
|
extensionManager
|
||||||
.get<ModelExtension>(ExtensionType.Model)
|
.get<ModelExtension>(ExtensionType.Model)
|
||||||
?.cancelModelDownload(item.modelId)
|
?.cancelModelDownload(item.modelId)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Fragment, useState, useEffect, useContext } from 'react'
|
import { Fragment, useState, useEffect } from 'react'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -11,7 +11,7 @@ import {
|
|||||||
CommandList,
|
CommandList,
|
||||||
} from '@janhq/uikit'
|
} from '@janhq/uikit'
|
||||||
|
|
||||||
import { useSetAtom } from 'jotai'
|
import { useAtomValue, useSetAtom } from 'jotai'
|
||||||
import {
|
import {
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
@ -22,18 +22,19 @@ import {
|
|||||||
|
|
||||||
import ShortCut from '@/containers/Shortcut'
|
import ShortCut from '@/containers/Shortcut'
|
||||||
|
|
||||||
import { FeatureToggleContext } from '@/context/FeatureToggle'
|
|
||||||
|
|
||||||
import { MainViewState } from '@/constants/screens'
|
import { MainViewState } from '@/constants/screens'
|
||||||
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
import { showRightSideBarAtom } from '@/screens/Chat/Sidebar'
|
||||||
|
|
||||||
|
import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom'
|
||||||
|
|
||||||
export default function CommandSearch() {
|
export default function CommandSearch() {
|
||||||
const { setMainViewState } = useMainViewState()
|
const { setMainViewState } = useMainViewState()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const setShowRightSideBar = useSetAtom(showRightSideBarAtom)
|
const setShowRightSideBar = useSetAtom(showRightSideBarAtom)
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
|
||||||
const menus = [
|
const menus = [
|
||||||
{
|
{
|
||||||
@ -123,13 +124,15 @@ export default function CommandSearch() {
|
|||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</CommandModal>
|
</CommandModal>
|
||||||
<Button
|
{activeThread && (
|
||||||
themes="outline"
|
<Button
|
||||||
className="unset-drag justify-start text-left text-xs font-normal text-muted-foreground focus:ring-0"
|
themes="outline"
|
||||||
onClick={() => setShowRightSideBar((show) => !show)}
|
className="unset-drag justify-start text-left text-xs font-normal text-muted-foreground focus:ring-0"
|
||||||
>
|
onClick={() => setShowRightSideBar((show) => !show)}
|
||||||
Toggle right
|
>
|
||||||
</Button>
|
Toggle right
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,34 +24,30 @@ import { extensionManager } from '@/extension'
|
|||||||
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
|
import { downloadingModelsAtom } from '@/helpers/atoms/Model.atom'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
suitableModel: Model
|
model: Model
|
||||||
isFromList?: boolean
|
isFromList?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ModalCancelDownload({
|
export default function ModalCancelDownload({ model, isFromList }: Props) {
|
||||||
suitableModel,
|
|
||||||
isFromList,
|
|
||||||
}: Props) {
|
|
||||||
const { modelDownloadStateAtom } = useDownloadState()
|
const { modelDownloadStateAtom } = useDownloadState()
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[suitableModel.name]),
|
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
[suitableModel.name]
|
[model.id]
|
||||||
)
|
)
|
||||||
const models = useAtomValue(downloadingModelsAtom)
|
const models = useAtomValue(downloadingModelsAtom)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
|
const cancelText = `Cancel ${formatDownloadPercentage(downloadState.percent)}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal>
|
<Modal>
|
||||||
<ModalTrigger asChild>
|
<ModalTrigger asChild>
|
||||||
{isFromList ? (
|
{isFromList ? (
|
||||||
<Button themes="outline" size="sm">
|
<Button themes="outline" size="sm">
|
||||||
Cancel ({formatDownloadPercentage(downloadState.percent)})
|
{cancelText}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button>
|
<Button>{cancelText}</Button>
|
||||||
Cancel ({formatDownloadPercentage(downloadState.percent)})
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</ModalTrigger>
|
</ModalTrigger>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
@ -60,7 +56,7 @@ export default function ModalCancelDownload({
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>
|
<p>
|
||||||
Are you sure you want to cancel the download of
|
Are you sure you want to cancel the download of
|
||||||
{downloadState?.fileName}?
|
{downloadState?.modelId}?
|
||||||
</p>
|
</p>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<div className="flex gap-x-2">
|
<div className="flex gap-x-2">
|
||||||
@ -71,11 +67,7 @@ export default function ModalCancelDownload({
|
|||||||
<Button
|
<Button
|
||||||
themes="danger"
|
themes="danger"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (downloadState?.fileName) {
|
if (downloadState?.modelId) {
|
||||||
const model = models.find(
|
|
||||||
(e) => e.id === downloadState?.fileName
|
|
||||||
)
|
|
||||||
if (!model) return
|
|
||||||
extensionManager
|
extensionManager
|
||||||
.get<ModelExtension>(ExtensionType.Model)
|
.get<ModelExtension>(ExtensionType.Model)
|
||||||
?.cancelModelDownload(downloadState.modelId)
|
?.cancelModelDownload(downloadState.modelId)
|
||||||
|
|||||||
@ -36,11 +36,11 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window && window.electronAPI) {
|
if (window && window.electronAPI) {
|
||||||
window.electronAPI.onFileDownloadUpdate(
|
window.electronAPI.onFileDownloadUpdate(
|
||||||
(_event: string, state: DownloadState | undefined) => {
|
(_event: string, state: any | undefined) => {
|
||||||
if (!state) return
|
if (!state) return
|
||||||
setDownloadState({
|
setDownloadState({
|
||||||
...state,
|
...state,
|
||||||
fileName: state.fileName.split('/').pop() ?? '',
|
modelId: state.fileName.split('/').pop() ?? '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -48,18 +48,18 @@ export default function EventListenerWrapper({ children }: PropsWithChildren) {
|
|||||||
window.electronAPI.onFileDownloadError(
|
window.electronAPI.onFileDownloadError(
|
||||||
(_event: string, callback: any) => {
|
(_event: string, callback: any) => {
|
||||||
console.error('Download error', callback)
|
console.error('Download error', callback)
|
||||||
const fileName = callback.fileName.split('/').pop() ?? ''
|
const modelId = callback.fileName.split('/').pop() ?? ''
|
||||||
setDownloadStateFailed(fileName)
|
setDownloadStateFailed(modelId)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
window.electronAPI.onFileDownloadSuccess(
|
window.electronAPI.onFileDownloadSuccess(
|
||||||
(_event: string, callback: any) => {
|
(_event: string, callback: any) => {
|
||||||
if (callback && callback.fileName) {
|
if (callback && callback.fileName) {
|
||||||
const fileName = callback.fileName.split('/').pop() ?? ''
|
const modelId = callback.fileName.split('/').pop() ?? ''
|
||||||
setDownloadStateSuccess(fileName)
|
setDownloadStateSuccess(modelId)
|
||||||
|
|
||||||
const model = modelsRef.current.find((e) => e.id === fileName)
|
const model = modelsRef.current.find((e) => e.id === modelId)
|
||||||
if (model)
|
if (model)
|
||||||
extensionManager
|
extensionManager
|
||||||
.get<ModelExtension>(ExtensionType.Model)
|
.get<ModelExtension>(ExtensionType.Model)
|
||||||
|
|||||||
@ -48,9 +48,8 @@ export function useActiveModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentTime = Date.now()
|
const currentTime = Date.now()
|
||||||
console.debug('Init model: ', modelId)
|
|
||||||
const res = await initModel(modelId, model?.settings)
|
const res = await initModel(modelId, model?.settings)
|
||||||
if (res && res.error && res.modelFile === stateModel.model) {
|
if (res && res.error) {
|
||||||
const errorMessage = `${res.error}`
|
const errorMessage = `${res.error}`
|
||||||
alert(errorMessage)
|
alert(errorMessage)
|
||||||
setStateModel(() => ({
|
setStateModel(() => ({
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Assistant,
|
Assistant,
|
||||||
|
ConversationalExtension,
|
||||||
|
ExtensionType,
|
||||||
Thread,
|
Thread,
|
||||||
ThreadAssistantInfo,
|
ThreadAssistantInfo,
|
||||||
ThreadState,
|
ThreadState,
|
||||||
@ -8,10 +10,13 @@ import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'
|
|||||||
|
|
||||||
import { generateThreadId } from '@/utils/conversation'
|
import { generateThreadId } from '@/utils/conversation'
|
||||||
|
|
||||||
|
import { extensionManager } from '@/extension'
|
||||||
import {
|
import {
|
||||||
threadsAtom,
|
threadsAtom,
|
||||||
setActiveThreadIdAtom,
|
setActiveThreadIdAtom,
|
||||||
threadStatesAtom,
|
threadStatesAtom,
|
||||||
|
activeThreadAtom,
|
||||||
|
updateThreadAtom,
|
||||||
} from '@/helpers/atoms/Conversation.atom'
|
} from '@/helpers/atoms/Conversation.atom'
|
||||||
|
|
||||||
const createNewThreadAtom = atom(null, (get, set, newThread: Thread) => {
|
const createNewThreadAtom = atom(null, (get, set, newThread: Thread) => {
|
||||||
@ -35,6 +40,8 @@ export const useCreateNewThread = () => {
|
|||||||
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
const setActiveThreadId = useSetAtom(setActiveThreadIdAtom)
|
||||||
const [threadStates, setThreadStates] = useAtom(threadStatesAtom)
|
const [threadStates, setThreadStates] = useAtom(threadStatesAtom)
|
||||||
const threads = useAtomValue(threadsAtom)
|
const threads = useAtomValue(threadsAtom)
|
||||||
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
|
const updateThread = useSetAtom(updateThreadAtom)
|
||||||
|
|
||||||
const requestCreateNewThread = async (assistant: Assistant) => {
|
const requestCreateNewThread = async (assistant: Assistant) => {
|
||||||
const unfinishedThreads = threads.filter((t) => t.isFinishInit === false)
|
const unfinishedThreads = threads.filter((t) => t.isFinishInit === false)
|
||||||
@ -86,7 +93,20 @@ export const useCreateNewThread = () => {
|
|||||||
setActiveThreadId(thread.id)
|
setActiveThreadId(thread.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateThreadTitle(title: string) {
|
||||||
|
if (!activeThread) return
|
||||||
|
const updatedConv: Thread = {
|
||||||
|
...activeThread,
|
||||||
|
title,
|
||||||
|
}
|
||||||
|
updateThread(updatedConv)
|
||||||
|
extensionManager
|
||||||
|
.get<ConversationalExtension>(ExtensionType.Conversational)
|
||||||
|
?.saveThread(updatedConv)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
requestCreateNewThread,
|
requestCreateNewThread,
|
||||||
|
updateThreadTitle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import { currentPromptAtom } from '@/containers/Providers/Jotai'
|
|||||||
|
|
||||||
import { toaster } from '@/containers/Toast'
|
import { toaster } from '@/containers/Toast'
|
||||||
|
|
||||||
import { extensionManager } from '../extension/ExtensionManager'
|
|
||||||
|
|
||||||
import { useActiveModel } from './useActiveModel'
|
import { useActiveModel } from './useActiveModel'
|
||||||
|
|
||||||
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
cleanConversationMessages,
|
cleanConversationMessages,
|
||||||
deleteConversationMessage,
|
deleteConversationMessage,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Model, ExtensionType, ModelExtension } from '@janhq/core'
|
import { Model, ExtensionType, ModelExtension } from '@janhq/core'
|
||||||
|
|
||||||
import { useAtom } from 'jotai'
|
import { useAtom, useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import { useDownloadState } from './useDownloadState'
|
import { useDownloadState } from './useDownloadState'
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ export default function useDownloadModel() {
|
|||||||
total: 0,
|
total: 0,
|
||||||
transferred: 0,
|
transferred: 0,
|
||||||
},
|
},
|
||||||
fileName: model.id,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setDownloadingModels([...downloadingModels, model])
|
setDownloadingModels([...downloadingModels, model])
|
||||||
|
|||||||
@ -8,39 +8,39 @@ const modelDownloadStateAtom = atom<Record<string, DownloadState>>({})
|
|||||||
const setDownloadStateAtom = atom(null, (get, set, state: DownloadState) => {
|
const setDownloadStateAtom = atom(null, (get, set, state: DownloadState) => {
|
||||||
const currentState = { ...get(modelDownloadStateAtom) }
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
console.debug(
|
console.debug(
|
||||||
`current download state for ${state.fileName} is ${JSON.stringify(state)}`
|
`current download state for ${state.modelId} is ${JSON.stringify(state)}`
|
||||||
)
|
)
|
||||||
currentState[state.fileName] = state
|
currentState[state.modelId] = state
|
||||||
set(modelDownloadStateAtom, currentState)
|
set(modelDownloadStateAtom, currentState)
|
||||||
})
|
})
|
||||||
|
|
||||||
const setDownloadStateSuccessAtom = atom(null, (get, set, fileName: string) => {
|
const setDownloadStateSuccessAtom = atom(null, (get, set, modelId: string) => {
|
||||||
const currentState = { ...get(modelDownloadStateAtom) }
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
const state = currentState[fileName]
|
const state = currentState[modelId]
|
||||||
if (!state) {
|
if (!state) {
|
||||||
console.error(`Cannot find download state for ${fileName}`)
|
console.error(`Cannot find download state for ${modelId}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete currentState[fileName]
|
delete currentState[modelId]
|
||||||
set(modelDownloadStateAtom, currentState)
|
set(modelDownloadStateAtom, currentState)
|
||||||
toaster({
|
toaster({
|
||||||
title: 'Download Completed',
|
title: 'Download Completed',
|
||||||
description: `Download ${fileName} completed`,
|
description: `Download ${modelId} completed`,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const setDownloadStateFailedAtom = atom(null, (get, set, fileName: string) => {
|
const setDownloadStateFailedAtom = atom(null, (get, set, modelId: string) => {
|
||||||
const currentState = { ...get(modelDownloadStateAtom) }
|
const currentState = { ...get(modelDownloadStateAtom) }
|
||||||
const state = currentState[fileName]
|
const state = currentState[modelId]
|
||||||
if (!state) {
|
if (!state) {
|
||||||
console.error(`Cannot find download state for ${fileName}`)
|
console.error(`Cannot find download state for ${modelId}`)
|
||||||
toaster({
|
toaster({
|
||||||
title: 'Cancel Download',
|
title: 'Cancel Download',
|
||||||
description: `Model ${fileName} cancel download`,
|
description: `Model ${modelId} cancel download`,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete currentState[fileName]
|
delete currentState[modelId]
|
||||||
set(modelDownloadStateAtom, currentState)
|
set(modelDownloadStateAtom, currentState)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,15 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { ExtensionType, ModelExtension } from '@janhq/core'
|
import { ExtensionType, ModelExtension } from '@janhq/core'
|
||||||
import { ModelCatalog } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
|
|
||||||
import { dummyModel } from '@/utils/dummy'
|
|
||||||
|
|
||||||
import { extensionManager } from '@/extension/ExtensionManager'
|
import { extensionManager } from '@/extension/ExtensionManager'
|
||||||
|
|
||||||
export async function getConfiguredModels(): Promise<ModelCatalog[]> {
|
|
||||||
return (
|
|
||||||
extensionManager
|
|
||||||
.get<ModelExtension>(ExtensionType.Model)
|
|
||||||
?.getConfiguredModels() ?? []
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGetConfiguredModels() {
|
export function useGetConfiguredModels() {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [models, setModels] = useState<ModelCatalog[]>([])
|
const [models, setModels] = useState<Model[]>([])
|
||||||
|
|
||||||
async function getConfiguredModels(): Promise<ModelCatalog[]> {
|
const getConfiguredModels = async (): Promise<Model[]> => {
|
||||||
const models = await extensionManager
|
const models = await extensionManager
|
||||||
.get<ModelExtension>(ExtensionType.Model)
|
.get<ModelExtension>(ExtensionType.Model)
|
||||||
?.getConfiguredModels()
|
?.getConfiguredModels()
|
||||||
@ -28,9 +18,9 @@ export function useGetConfiguredModels() {
|
|||||||
|
|
||||||
async function fetchModels() {
|
async function fetchModels() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
let models = await getConfiguredModels()
|
const models = await getConfiguredModels()
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
models = [dummyModel, ...models]
|
// models = [dummyModel, ...models] // TODO: NamH add back dummy model later
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
setModels(models)
|
setModels(models)
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
import { Model } from '@janhq/core'
|
|
||||||
import { useAtomValue } from 'jotai'
|
|
||||||
|
|
||||||
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
|
||||||
|
|
||||||
export default function useGetMostSuitableModelVersion() {
|
|
||||||
const [suitableModel, setSuitableModel] = useState<Model | undefined>()
|
|
||||||
const totalRam = useAtomValue(totalRamAtom)
|
|
||||||
|
|
||||||
const getMostSuitableModelVersion = async (modelVersions: Model[]) => {
|
|
||||||
// find the model version with the highest required RAM that is still below the user's RAM by 80%
|
|
||||||
const modelVersion = modelVersions.reduce((prev, current) => {
|
|
||||||
if (current.metadata.maxRamRequired > prev.metadata.maxRamRequired) {
|
|
||||||
if (current.metadata.maxRamRequired < totalRam * 0.8) {
|
|
||||||
return current
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prev
|
|
||||||
})
|
|
||||||
|
|
||||||
setSuitableModel(modelVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { suitableModel, getMostSuitableModelVersion }
|
|
||||||
}
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
import { Model } from '@janhq/core'
|
|
||||||
|
|
||||||
import { ModelPerformance, TagType } from '@/constants/tagType'
|
|
||||||
|
|
||||||
// Recommendation:
|
|
||||||
// `Recommended (green)`: "Max RAM required" is 80% of users max RAM.
|
|
||||||
// `Slow on your device (yellow)`: Max RAM required is 80-100% of users max RAM
|
|
||||||
// `Not enough RAM (red)`: User RAM is below "Max RAM required"
|
|
||||||
|
|
||||||
export default function useGetPerformanceTag() {
|
|
||||||
async function getPerformanceForModel(
|
|
||||||
model: Model,
|
|
||||||
totalRam: number
|
|
||||||
): Promise<{ title: string; performanceTag: TagType }> {
|
|
||||||
const requiredRam = model.metadata.maxRamRequired
|
|
||||||
const performanceTag = calculateRamPerformance(requiredRam, totalRam)
|
|
||||||
|
|
||||||
let title = ''
|
|
||||||
|
|
||||||
switch (performanceTag) {
|
|
||||||
case ModelPerformance.PerformancePositive:
|
|
||||||
title = 'Recommended'
|
|
||||||
break
|
|
||||||
case ModelPerformance.PerformanceNeutral:
|
|
||||||
title = 'Slow on your device'
|
|
||||||
break
|
|
||||||
case ModelPerformance.PerformanceNegative:
|
|
||||||
title = 'Not enough RAM'
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return { title, performanceTag }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { getPerformanceForModel }
|
|
||||||
}
|
|
||||||
|
|
||||||
const calculateRamPerformance = (
|
|
||||||
requiredRamAmt: number,
|
|
||||||
totalRamAmt: number
|
|
||||||
) => {
|
|
||||||
const percentage = requiredRamAmt / totalRamAmt
|
|
||||||
|
|
||||||
if (percentage < 0.8) {
|
|
||||||
return ModelPerformance.PerformancePositive
|
|
||||||
} else if (percentage >= 0.8 && percentage < 1) {
|
|
||||||
return ModelPerformance.PerformanceNeutral
|
|
||||||
} else {
|
|
||||||
return ModelPerformance.PerformanceNegative
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -45,48 +45,6 @@ export default function useSendChatMessage() {
|
|||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
const { startModel } = useActiveModel()
|
const { startModel } = useActiveModel()
|
||||||
|
|
||||||
function updateThreadTitle(newMessage: MessageRequest) {
|
|
||||||
if (
|
|
||||||
activeThread &&
|
|
||||||
newMessage.messages &&
|
|
||||||
newMessage.messages.length > 2 &&
|
|
||||||
(activeThread.title === '' || activeThread.title === activeModel?.name)
|
|
||||||
) {
|
|
||||||
const summaryMsg: ChatCompletionMessage = {
|
|
||||||
role: ChatCompletionRole.User,
|
|
||||||
content:
|
|
||||||
'Summarize this conversation in less than 5 words, the response should just include the summary',
|
|
||||||
}
|
|
||||||
// Request convo summary
|
|
||||||
setTimeout(async () => {
|
|
||||||
const result = await extensionManager
|
|
||||||
.get<InferenceExtension>(ExtensionType.Inference)
|
|
||||||
?.inferenceRequest({
|
|
||||||
...newMessage,
|
|
||||||
messages: newMessage.messages?.slice(0, -1).concat([summaryMsg]),
|
|
||||||
})
|
|
||||||
.catch(console.error)
|
|
||||||
const content = result?.content[0]?.text.value.trim()
|
|
||||||
if (
|
|
||||||
activeThread &&
|
|
||||||
activeThread.id === newMessage.threadId &&
|
|
||||||
content &&
|
|
||||||
content.length > 0 &&
|
|
||||||
content.split(' ').length <= 20
|
|
||||||
) {
|
|
||||||
const updatedConv: Thread = {
|
|
||||||
...activeThread,
|
|
||||||
title: content,
|
|
||||||
}
|
|
||||||
updateThread(updatedConv)
|
|
||||||
extensionManager
|
|
||||||
.get<ConversationalExtension>(ExtensionType.Conversational)
|
|
||||||
?.saveThread(updatedConv)
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sendChatMessage = async () => {
|
const sendChatMessage = async () => {
|
||||||
if (!currentPrompt || currentPrompt.trim().length === 0) {
|
if (!currentPrompt || currentPrompt.trim().length === 0) {
|
||||||
return
|
return
|
||||||
@ -172,7 +130,6 @@ export default function useSendChatMessage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addNewMessage(threadMessage)
|
addNewMessage(threadMessage)
|
||||||
updateThreadTitle(messageRequest)
|
|
||||||
|
|
||||||
await extensionManager
|
await extensionManager
|
||||||
.get<ConversationalExtension>(ExtensionType.Conversational)
|
.get<ConversationalExtension>(ExtensionType.Conversational)
|
||||||
@ -180,6 +137,10 @@ export default function useSendChatMessage() {
|
|||||||
|
|
||||||
const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id
|
const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id
|
||||||
if (activeModel?.id !== modelId) {
|
if (activeModel?.id !== modelId) {
|
||||||
|
toaster({
|
||||||
|
title: 'Message queued.',
|
||||||
|
description: 'It will be sent once the model is done loading',
|
||||||
|
})
|
||||||
await startModel(modelId)
|
await startModel(modelId)
|
||||||
}
|
}
|
||||||
events.emit(EventName.OnMessageSent, messageRequest)
|
events.emit(EventName.OnMessageSent, messageRequest)
|
||||||
|
|||||||
@ -22,11 +22,6 @@ export default function useSetActiveThread() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!thread.isFinishInit) {
|
|
||||||
console.debug('Thread not finish init')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the corresponding messages
|
// load the corresponding messages
|
||||||
const messages = await extensionManager
|
const messages = await extensionManager
|
||||||
.get<ConversationalExtension>(ExtensionType.Conversational)
|
.get<ConversationalExtension>(ExtensionType.Conversational)
|
||||||
|
|||||||
@ -43,8 +43,10 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
|
|||||||
.get<InferenceExtension>(ExtensionType.Inference)
|
.get<InferenceExtension>(ExtensionType.Inference)
|
||||||
?.stopInference()
|
?.stopInference()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
message.status = MessageStatus.Ready
|
events.emit(EventName.OnMessageUpdate, {
|
||||||
events.emit(EventName.OnMessageUpdate, message)
|
...message,
|
||||||
|
status: MessageStatus.Ready,
|
||||||
|
})
|
||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import DropdownListSidebar, {
|
|||||||
} from '@/containers/DropdownListSidebar'
|
} from '@/containers/DropdownListSidebar'
|
||||||
import ItemCardSidebar from '@/containers/ItemCardSidebar'
|
import ItemCardSidebar from '@/containers/ItemCardSidebar'
|
||||||
|
|
||||||
|
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||||
|
|
||||||
import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom'
|
import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom'
|
||||||
|
|
||||||
export const showRightSideBarAtom = atom<boolean>(false)
|
export const showRightSideBarAtom = atom<boolean>(false)
|
||||||
@ -17,6 +19,7 @@ export default function Sidebar() {
|
|||||||
const showing = useAtomValue(showRightSideBarAtom)
|
const showing = useAtomValue(showRightSideBarAtom)
|
||||||
const activeThread = useAtomValue(activeThreadAtom)
|
const activeThread = useAtomValue(activeThreadAtom)
|
||||||
const selectedModel = useAtomValue(selectedModelAtom)
|
const selectedModel = useAtomValue(selectedModelAtom)
|
||||||
|
const { updateThreadTitle } = useCreateNewThread()
|
||||||
|
|
||||||
const onReviewInFinderClick = async (type: string) => {
|
const onReviewInFinderClick = async (type: string) => {
|
||||||
if (!activeThread) return
|
if (!activeThread) return
|
||||||
@ -47,7 +50,6 @@ export default function Sidebar() {
|
|||||||
if (!filePath) return
|
if (!filePath) return
|
||||||
|
|
||||||
const fullPath = join(userSpace, filePath)
|
const fullPath = join(userSpace, filePath)
|
||||||
console.log(fullPath)
|
|
||||||
openFileExplorer(fullPath)
|
openFileExplorer(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +82,6 @@ export default function Sidebar() {
|
|||||||
if (!filePath) return
|
if (!filePath) return
|
||||||
|
|
||||||
const fullPath = join(userSpace, filePath)
|
const fullPath = join(userSpace, filePath)
|
||||||
console.log(fullPath)
|
|
||||||
openFileExplorer(fullPath)
|
openFileExplorer(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,8 +97,16 @@ export default function Sidebar() {
|
|||||||
onRevealInFinderClick={onReviewInFinderClick}
|
onRevealInFinderClick={onReviewInFinderClick}
|
||||||
onViewJsonClick={onViewJsonClick}
|
onViewJsonClick={onViewJsonClick}
|
||||||
>
|
>
|
||||||
<ItemCardSidebar description={activeThread?.id} title="Thread ID" />
|
<ItemCardSidebar
|
||||||
<ItemCardSidebar title="Thread title" />
|
description={activeThread?.id}
|
||||||
|
title="Thread ID"
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
<ItemCardSidebar
|
||||||
|
title="Thread title"
|
||||||
|
description={activeThread?.title}
|
||||||
|
onChange={(title) => updateThreadTitle(title ?? '')}
|
||||||
|
/>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
<CardSidebar
|
<CardSidebar
|
||||||
title="Assistant"
|
title="Assistant"
|
||||||
@ -107,6 +116,7 @@ export default function Sidebar() {
|
|||||||
<ItemCardSidebar
|
<ItemCardSidebar
|
||||||
description={activeThread?.assistants[0].assistant_name ?? ''}
|
description={activeThread?.assistants[0].assistant_name ?? ''}
|
||||||
title="Assistant"
|
title="Assistant"
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
</CardSidebar>
|
</CardSidebar>
|
||||||
<CardSidebar
|
<CardSidebar
|
||||||
|
|||||||
@ -1,71 +1,46 @@
|
|||||||
/* eslint-disable react/display-name */
|
/* eslint-disable react/display-name */
|
||||||
|
|
||||||
import { forwardRef, useEffect, useState } from 'react'
|
import { forwardRef } from 'react'
|
||||||
|
|
||||||
import { ModelCatalog } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
import { Badge } from '@janhq/uikit'
|
import { Badge } from '@janhq/uikit'
|
||||||
|
|
||||||
import useGetMostSuitableModelVersion from '@/hooks/useGetMostSuitableModelVersion'
|
|
||||||
|
|
||||||
import ExploreModelItemHeader from '@/screens/ExploreModels/ExploreModelItemHeader'
|
import ExploreModelItemHeader from '@/screens/ExploreModels/ExploreModelItemHeader'
|
||||||
import ModelVersionList from '@/screens/ExploreModels/ModelVersionList'
|
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
|
||||||
import { displayDate } from '@/utils/datetime'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
model: ModelCatalog
|
model: Model
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
||||||
const [show, setShow] = useState(false)
|
|
||||||
|
|
||||||
const { availableVersions } = model
|
|
||||||
const { suitableModel, getMostSuitableModelVersion } =
|
|
||||||
useGetMostSuitableModelVersion()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getMostSuitableModelVersion(availableVersions)
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [availableVersions])
|
|
||||||
|
|
||||||
if (!suitableModel) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className="mb-4 flex flex-col rounded-md border border-border bg-background/60"
|
className="mb-4 flex flex-col rounded-md border border-border bg-background/60"
|
||||||
>
|
>
|
||||||
<ExploreModelItemHeader
|
<ExploreModelItemHeader model={model} />
|
||||||
suitableModel={suitableModel}
|
|
||||||
exploreModel={model}
|
|
||||||
/>
|
|
||||||
<div className="flex flex-col p-4">
|
<div className="flex flex-col p-4">
|
||||||
<div className="mb-4 flex flex-col gap-1">
|
<div className="mb-4 flex flex-col gap-1">
|
||||||
<span className="font-semibold">About</span>
|
<span className="font-semibold">About</span>
|
||||||
<p>{model.longDescription}</p>
|
<p>{model.description}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4 flex space-x-6 border-b border-border pb-4">
|
<div className="mb-4 flex space-x-6 border-b border-border pb-4">
|
||||||
<div>
|
<div>
|
||||||
<span className="font-semibold">Author</span>
|
<span className="font-semibold">Author</span>
|
||||||
<p className="mt-1 font-medium">{model.author}</p>
|
<p className="mt-1 font-medium">{model.metadata.author}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="mb-1 font-semibold">Compatibility</span>
|
<span className="mb-1 font-semibold">Compatibility</span>
|
||||||
<div className="mt-1 flex gap-2">
|
<div className="mt-1 flex gap-2">
|
||||||
<Badge
|
{/* <Badge
|
||||||
themes="secondary"
|
themes="secondary"
|
||||||
className="line-clamp-1 lg:line-clamp-none"
|
className="line-clamp-1 lg:line-clamp-none"
|
||||||
title={`${toGigabytes(
|
title={`${toGigabytes(
|
||||||
suitableModel.metadata.maxRamRequired
|
model.metadata.maxRamRequired // TODO: check this
|
||||||
)} RAM required`}
|
)} RAM required`}
|
||||||
>
|
>
|
||||||
{toGigabytes(suitableModel.metadata.maxRamRequired)} RAM
|
{toGigabytes(model.metadata.maxRamRequired)} RAM required
|
||||||
required
|
</Badge> */}
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,21 +50,12 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
|||||||
<span className="font-semibold">Version</span>
|
<span className="font-semibold">Version</span>
|
||||||
<div className="mt-2 flex space-x-2">
|
<div className="mt-2 flex space-x-2">
|
||||||
<Badge themes="outline">v{model.version}</Badge>
|
<Badge themes="outline">v{model.version}</Badge>
|
||||||
{suitableModel.metadata.quantization && (
|
|
||||||
<Badge themes="outline">
|
|
||||||
{suitableModel.metadata.quantization}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<span className="font-semibold">Release Date</span>
|
|
||||||
<p className="mt-1 ">{displayDate(model.releaseDate)}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<span className="font-semibold">Tags</span>
|
<span className="font-semibold">Tags</span>
|
||||||
<div className="mt-2 flex space-x-2">
|
<div className="mt-2 flex space-x-2">
|
||||||
{model.tags.map((tag, i) => (
|
{model.metadata.tags.map((tag, i) => (
|
||||||
<Badge key={i} themes="outline">
|
<Badge key={i} themes="outline">
|
||||||
{tag}
|
{tag}
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -97,23 +63,6 @@ const ExploreModelItem = forwardRef<HTMLDivElement, Props>(({ model }, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{model.availableVersions?.length > 0 && (
|
|
||||||
<div className="mt-5 w-full rounded-md border border-border bg-background p-2">
|
|
||||||
<button onClick={() => setShow(!show)} className="w-full">
|
|
||||||
{!show
|
|
||||||
? '+ Show Available Versions'
|
|
||||||
: '- Collapse Available Versions'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{show && (
|
|
||||||
<ModelVersionList
|
|
||||||
models={model.availableVersions}
|
|
||||||
recommendedVersion={suitableModel?.name ?? ''}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
/* 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 { Badge, Button } from '@janhq/uikit'
|
||||||
|
|
||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
@ -15,67 +15,41 @@ import { ModelPerformance, TagType } from '@/constants/tagType'
|
|||||||
import useDownloadModel from '@/hooks/useDownloadModel'
|
import useDownloadModel from '@/hooks/useDownloadModel'
|
||||||
import { useDownloadState } from '@/hooks/useDownloadState'
|
import { useDownloadState } from '@/hooks/useDownloadState'
|
||||||
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
import { useGetDownloadedModels } from '@/hooks/useGetDownloadedModels'
|
||||||
import useGetPerformanceTag from '@/hooks/useGetPerformanceTag'
|
|
||||||
import { useMainViewState } from '@/hooks/useMainViewState'
|
import { useMainViewState } from '@/hooks/useMainViewState'
|
||||||
|
|
||||||
import { toGigabytes } from '@/utils/converter'
|
import { toGigabytes } from '@/utils/converter'
|
||||||
|
|
||||||
import { totalRamAtom } from '@/helpers/atoms/SystemBar.atom'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
suitableModel: Model
|
model: Model
|
||||||
exploreModel: ModelCatalog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelItemHeader: React.FC<Props> = ({
|
const ExploreModelItemHeader: React.FC<Props> = ({ model }) => {
|
||||||
suitableModel,
|
|
||||||
exploreModel,
|
|
||||||
}) => {
|
|
||||||
const { downloadModel } = useDownloadModel()
|
const { downloadModel } = useDownloadModel()
|
||||||
const { downloadedModels } = useGetDownloadedModels()
|
const { downloadedModels } = useGetDownloadedModels()
|
||||||
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
|
const { modelDownloadStateAtom, downloadStates } = useDownloadState()
|
||||||
const { getPerformanceForModel } = useGetPerformanceTag()
|
|
||||||
const [title, setTitle] = useState<string>('Recommended')
|
const [title, setTitle] = useState<string>('Recommended')
|
||||||
const totalRam = useAtomValue(totalRamAtom)
|
|
||||||
const [performanceTag, setPerformanceTag] = useState<TagType>(
|
const [performanceTag, setPerformanceTag] = useState<TagType>(
|
||||||
ModelPerformance.PerformancePositive
|
ModelPerformance.PerformancePositive
|
||||||
)
|
)
|
||||||
const downloadAtom = useMemo(
|
const downloadAtom = useMemo(
|
||||||
() => atom((get) => get(modelDownloadStateAtom)[suitableModel.name]),
|
() => atom((get) => get(modelDownloadStateAtom)[model.id]),
|
||||||
[suitableModel.name]
|
[model.id]
|
||||||
)
|
)
|
||||||
const downloadState = useAtomValue(downloadAtom)
|
const downloadState = useAtomValue(downloadAtom)
|
||||||
const { setMainViewState } = useMainViewState()
|
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(() => {
|
const onDownloadClick = useCallback(() => {
|
||||||
downloadModel(suitableModel)
|
downloadModel(model)
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [suitableModel])
|
}, [model])
|
||||||
|
|
||||||
// TODO: Comparing between Model Id and Version Name?
|
const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null
|
||||||
const isDownloaded =
|
|
||||||
downloadedModels.find((model) => model.id === suitableModel.name) != null
|
|
||||||
|
|
||||||
let downloadButton = (
|
let downloadButton = (
|
||||||
<Button onClick={() => onDownloadClick()}>
|
<Button onClick={() => onDownloadClick()}>
|
||||||
{suitableModel.metadata.size
|
{model.metadata.size
|
||||||
? `Download (${toGigabytes(suitableModel.metadata.size)})`
|
? `Download (${toGigabytes(model.metadata.size)})`
|
||||||
: 'Download'}
|
: 'Download'}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@ -93,7 +67,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (downloadState != null && downloadStates.length > 0) {
|
if (downloadState != null && downloadStates.length > 0) {
|
||||||
downloadButton = <ModalCancelDownload suitableModel={suitableModel} />
|
downloadButton = <ModalCancelDownload model={model} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderBadge = (performance: TagType) => {
|
const renderBadge = (performance: TagType) => {
|
||||||
@ -115,7 +89,7 @@ const ExploreModelItemHeader: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-between rounded-t-md border-b border-border bg-background/50 px-4 py-2">
|
<div className="flex items-center justify-between rounded-t-md border-b border-border bg-background/50 px-4 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-medium">{exploreModel.name}</span>
|
<span className="font-medium">{model.name}</span>
|
||||||
{performanceTag && renderBadge(performanceTag)}
|
{performanceTag && renderBadge(performanceTag)}
|
||||||
</div>
|
</div>
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { ModelCatalog } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
|
|
||||||
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
import ExploreModelItem from '@/screens/ExploreModels/ExploreModelItem'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
models: ModelCatalog[]
|
models: Model[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExploreModelList: React.FC<Props> = ({ models }) => (
|
const ExploreModelList: React.FC<Props> = ({ models }) => (
|
||||||
<div className="relative h-full w-full flex-shrink-0">
|
<div className="relative h-full w-full flex-shrink-0">
|
||||||
{models?.map((item, i) => (
|
{models?.map((model) => <ExploreModelItem key={model.id} model={model} />)}
|
||||||
<ExploreModelItem key={item.name + '/' + item.id} model={item} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
|
|
||||||
import { Model } from '@janhq/core'
|
import { Model } from '@janhq/core'
|
||||||
import { Badge, Button } from '@janhq/uikit'
|
import { Button } from '@janhq/uikit'
|
||||||
import { atom, useAtomValue } from 'jotai'
|
import { atom, useAtomValue } from 'jotai'
|
||||||
|
|
||||||
import ModalCancelDownload from '@/containers/ModalCancelDownload'
|
import ModalCancelDownload from '@/containers/ModalCancelDownload'
|
||||||
@ -63,7 +63,7 @@ const ModelVersionItem: React.FC<Props> = ({ model }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (downloadState != null && downloadStates.length > 0) {
|
if (downloadState != null && downloadStates.length > 0) {
|
||||||
downloadButton = <ModalCancelDownload suitableModel={model} isFromList />
|
downloadButton = <ModalCancelDownload model={model} isFromList />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -74,16 +74,7 @@ const ModelVersionItem: React.FC<Props> = ({ model }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex justify-end gap-2">
|
<div className="flex justify-end gap-2"></div>
|
||||||
<Badge
|
|
||||||
themes="secondary"
|
|
||||||
className="line-clamp-1"
|
|
||||||
title={`${toGigabytes(model.metadata.maxRamRequired)} RAM required`}
|
|
||||||
>{`${toGigabytes(
|
|
||||||
model.metadata.maxRamRequired
|
|
||||||
)} RAM required`}</Badge>
|
|
||||||
<Badge themes="secondary">{toGigabytes(model.metadata.size)}</Badge>
|
|
||||||
</div>
|
|
||||||
{downloadButton}
|
{downloadButton}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -55,7 +55,7 @@ export default function BlankStateMyModel() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p>{item?.fileName}</p>
|
<p>{item?.modelId}</p>
|
||||||
<span>{formatDownloadPercentage(item?.percent)}</span>
|
<span>{formatDownloadPercentage(item?.percent)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -63,10 +63,7 @@ const MyModelsScreen = () => {
|
|||||||
<div className="flex items-start gap-x-4">
|
<div className="flex items-start gap-x-4">
|
||||||
<div className="inline-flex rounded-full border border-border p-1">
|
<div className="inline-flex rounded-full border border-border p-1">
|
||||||
<Avatar className="h-8 w-8">
|
<Avatar className="h-8 w-8">
|
||||||
<AvatarImage
|
<AvatarImage alt={model.metadata.author} />
|
||||||
src={model.metadata.avatarUrl}
|
|
||||||
alt={model.metadata.author}
|
|
||||||
/>
|
|
||||||
<AvatarFallback>
|
<AvatarFallback>
|
||||||
{model.metadata.author.charAt(0)}
|
{model.metadata.author.charAt(0)}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
|
|||||||
@ -30,7 +30,6 @@ const SettingsScreen = () => {
|
|||||||
setMenus(menu)
|
setMenus(menu)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
const preferenceExtensions = preferenceItems
|
const preferenceExtensions = preferenceItems
|
||||||
.map((x) => x.extensionnName)
|
.map((x) => x.extensionnName)
|
||||||
.filter((x, i) => {
|
.filter((x, i) => {
|
||||||
|
|||||||
1
web/types/downloadState.d.ts
vendored
1
web/types/downloadState.d.ts
vendored
@ -4,7 +4,6 @@ type DownloadState = {
|
|||||||
speed: number
|
speed: number
|
||||||
percent: number
|
percent: number
|
||||||
size: DownloadSize
|
size: DownloadSize
|
||||||
fileName: string
|
|
||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user