From ed5413a1eea38710f1608b2cd172c3ce28d31dca Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Tue, 28 Nov 2023 00:18:36 +0700 Subject: [PATCH 001/179] Base server code (mirrored from electron + removed unnecessary handlers --- server/core/plugin/facade.ts | 30 +++++ server/core/plugin/globals.ts | 36 ++++++ server/core/plugin/index.ts | 149 +++++++++++++++++++++ server/core/plugin/plugin.ts | 213 +++++++++++++++++++++++++++++++ server/core/plugin/router.ts | 97 ++++++++++++++ server/core/plugin/store.ts | 131 +++++++++++++++++++ server/core/pre-install/.gitkeep | 0 server/handlers/download.ts | 108 ++++++++++++++++ server/handlers/fs.ts | 156 ++++++++++++++++++++++ server/handlers/plugin.ts | 118 +++++++++++++++++ server/icons/icon.png | Bin 0 -> 38651 bytes server/main.ts | 32 +++++ server/managers/download.ts | 24 ++++ server/managers/module.ts | 33 +++++ server/managers/plugin.ts | 60 +++++++++ server/managers/window.ts | 37 ++++++ server/package.json | 97 ++++++++++++++ server/tsconfig.json | 20 +++ server/utils/disposable.ts | 8 ++ server/utils/menu.ts | 111 ++++++++++++++++ server/utils/versionDiff.ts | 21 +++ 21 files changed, 1481 insertions(+) create mode 100644 server/core/plugin/facade.ts create mode 100644 server/core/plugin/globals.ts create mode 100644 server/core/plugin/index.ts create mode 100644 server/core/plugin/plugin.ts create mode 100644 server/core/plugin/router.ts create mode 100644 server/core/plugin/store.ts create mode 100644 server/core/pre-install/.gitkeep create mode 100644 server/handlers/download.ts create mode 100644 server/handlers/fs.ts create mode 100644 server/handlers/plugin.ts create mode 100644 server/icons/icon.png create mode 100644 server/main.ts create mode 100644 server/managers/download.ts create mode 100644 server/managers/module.ts create mode 100644 server/managers/plugin.ts create mode 100644 server/managers/window.ts create mode 100644 server/package.json create mode 100644 server/tsconfig.json create mode 100644 server/utils/disposable.ts create mode 100644 server/utils/menu.ts create mode 100644 server/utils/versionDiff.ts diff --git a/server/core/plugin/facade.ts b/server/core/plugin/facade.ts new file mode 100644 index 000000000..bd1089109 --- /dev/null +++ b/server/core/plugin/facade.ts @@ -0,0 +1,30 @@ +const { ipcRenderer, contextBridge } = require("electron"); + +export function useFacade() { + const interfaces = { + install(plugins: any[]) { + return ipcRenderer.invoke("pluggable:install", plugins); + }, + uninstall(plugins: any[], reload: boolean) { + return ipcRenderer.invoke("pluggable:uninstall", plugins, reload); + }, + getActive() { + return ipcRenderer.invoke("pluggable:getActivePlugins"); + }, + update(plugins: any[], reload: boolean) { + return ipcRenderer.invoke("pluggable:update", plugins, reload); + }, + updatesAvailable(plugin: any) { + return ipcRenderer.invoke("pluggable:updatesAvailable", plugin); + }, + toggleActive(plugin: any, active: boolean) { + return ipcRenderer.invoke("pluggable:togglePluginActive", plugin, active); + }, + }; + + if (contextBridge) { + contextBridge.exposeInMainWorld("pluggableElectronIpc", interfaces); + } + + return interfaces; +} diff --git a/server/core/plugin/globals.ts b/server/core/plugin/globals.ts new file mode 100644 index 000000000..69df7925c --- /dev/null +++ b/server/core/plugin/globals.ts @@ -0,0 +1,36 @@ +import { existsSync, mkdirSync, writeFileSync } from "fs"; +import { join, resolve } from "path"; + +export let pluginsPath: string | undefined = undefined; + +/** + * @private + * Set path to plugins directory and create the directory if it does not exist. + * @param {string} plgPath path to plugins directory + */ +export function setPluginsPath(plgPath: string) { + // Create folder if it does not exist + let plgDir; + try { + plgDir = resolve(plgPath); + if (plgDir.length < 2) throw new Error(); + + if (!existsSync(plgDir)) mkdirSync(plgDir); + + const pluginsJson = join(plgDir, "plugins.json"); + if (!existsSync(pluginsJson)) writeFileSync(pluginsJson, "{}", "utf8"); + + pluginsPath = plgDir; + } catch (error) { + throw new Error("Invalid path provided to the plugins folder"); + } +} + +/** + * @private + * Get the path to the plugins.json file. + * @returns location of plugins.json + */ +export function getPluginsFile() { + return join(pluginsPath ?? "", "plugins.json"); +} \ No newline at end of file diff --git a/server/core/plugin/index.ts b/server/core/plugin/index.ts new file mode 100644 index 000000000..e8c64747b --- /dev/null +++ b/server/core/plugin/index.ts @@ -0,0 +1,149 @@ +import { readFileSync } from "fs"; +import { protocol } from "electron"; +import { normalize } from "path"; + +import Plugin from "./plugin"; +import { + getAllPlugins, + removePlugin, + persistPlugins, + installPlugins, + getPlugin, + getActivePlugins, + addPlugin, +} from "./store"; +import { + pluginsPath as storedPluginsPath, + setPluginsPath, + getPluginsFile, +} from "./globals"; +import router from "./router"; + +/** + * Sets up the required communication between the main and renderer processes. + * Additionally sets the plugins up using {@link usePlugins} if a pluginsPath is provided. + * @param {Object} options configuration for setting up the renderer facade. + * @param {confirmInstall} [options.confirmInstall] Function to validate that a plugin should be installed. + * @param {Boolean} [options.useFacade=true] Whether to make a facade to the plugins available in the renderer. + * @param {string} [options.pluginsPath] Optional path to the plugins folder. + * @returns {pluginManager|Object} A set of functions used to manage the plugin lifecycle if usePlugins is provided. + * @function + */ +export function init(options: any) { + if ( + !Object.prototype.hasOwnProperty.call(options, "useFacade") || + options.useFacade + ) { + // Enable IPC to be used by the facade + router(); + } + + // Create plugins protocol to serve plugins to renderer + registerPluginProtocol(); + + // perform full setup if pluginsPath is provided + if (options.pluginsPath) { + return usePlugins(options.pluginsPath); + } + + return {}; +} + +/** + * Create plugins protocol to provide plugins to renderer + * @private + * @returns {boolean} Whether the protocol registration was successful + */ +function registerPluginProtocol() { + return protocol.registerFileProtocol("plugin", (request, callback) => { + const entry = request.url.substr(8); + const url = normalize(storedPluginsPath + entry); + callback({ path: url }); + }); +} + +/** + * Set Pluggable Electron up to run from the pluginPath folder if it is provided and + * load plugins persisted in that folder. + * @param {string} pluginsPath Path to the plugins folder. Required if not yet set up. + * @returns {pluginManager} A set of functions used to manage the plugin lifecycle. + */ +export function usePlugins(pluginsPath: string) { + if (!pluginsPath) + throw Error( + "A path to the plugins folder is required to use Pluggable Electron" + ); + // Store the path to the plugins folder + setPluginsPath(pluginsPath); + + // Remove any registered plugins + for (const plugin of getAllPlugins()) { + if (plugin.name) removePlugin(plugin.name, false); + } + + // Read plugin list from plugins folder + const plugins = JSON.parse(readFileSync(getPluginsFile(), "utf-8")); + try { + // Create and store a Plugin instance for each plugin in list + for (const p in plugins) { + loadPlugin(plugins[p]); + } + persistPlugins(); + } catch (error) { + // Throw meaningful error if plugin loading fails + throw new Error( + "Could not successfully rebuild list of installed plugins.\n" + + error + + "\nPlease check the plugins.json file in the plugins folder." + ); + } + + // Return the plugin lifecycle functions + return getStore(); +} + +/** + * Check the given plugin object. If it is marked for uninstalling, the plugin files are removed. + * Otherwise a Plugin instance for the provided object is created and added to the store. + * @private + * @param {Object} plg Plugin info + */ +function loadPlugin(plg: any) { + // Create new plugin, populate it with plg details and save it to the store + const plugin = new Plugin(); + + for (const key in plg) { + if (Object.prototype.hasOwnProperty.call(plg, key)) { + // Use Object.defineProperty to set the properties as writable + Object.defineProperty(plugin, key, { + value: plg[key], + writable: true, + enumerable: true, + configurable: true, + }); + } + } + + addPlugin(plugin, false); + plugin.subscribe("pe-persist", persistPlugins); +} + +/** + * Returns the publicly available store functions. + * @returns {pluginManager} A set of functions used to manage the plugin lifecycle. + */ +export function getStore() { + if (!storedPluginsPath) { + throw new Error( + "The plugin path has not yet been set up. Please run usePlugins before accessing the store" + ); + } + + return { + installPlugins, + getPlugin, + getAllPlugins, + getActivePlugins, + removePlugin, + }; +} diff --git a/server/core/plugin/plugin.ts b/server/core/plugin/plugin.ts new file mode 100644 index 000000000..f0fc073d7 --- /dev/null +++ b/server/core/plugin/plugin.ts @@ -0,0 +1,213 @@ +import { rmdir } from "fs/promises"; +import { resolve, join } from "path"; +import { manifest, extract } from "pacote"; +import * as Arborist from "@npmcli/arborist"; + +import { pluginsPath } from "./globals"; + +/** + * An NPM package that can be used as a Pluggable Electron plugin. + * Used to hold all the information and functions necessary to handle the plugin lifecycle. + */ +class Plugin { + /** + * @property {string} origin Original specification provided to fetch the package. + * @property {Object} installOptions Options provided to pacote when fetching the manifest. + * @property {name} name The name of the plugin as defined in the manifest. + * @property {string} url Electron URL where the package can be accessed. + * @property {string} version Version of the package as defined in the manifest. + * @property {Array} activationPoints List of {@link ./Execution-API#activationPoints|activation points}. + * @property {string} main The entry point as defined in the main entry of the manifest. + * @property {string} description The description of plugin as defined in the manifest. + * @property {string} icon The icon of plugin as defined in the manifest. + */ + origin?: string; + installOptions: any; + name?: string; + url?: string; + version?: string; + activationPoints?: Array; + main?: string; + description?: string; + icon?: string; + + /** @private */ + _active = false; + + /** + * @private + * @property {Object.} #listeners A list of callbacks to be executed when the Plugin is updated. + */ + listeners: Record void> = {}; + + /** + * Set installOptions with defaults for options that have not been provided. + * @param {string} [origin] Original specification provided to fetch the package. + * @param {Object} [options] Options provided to pacote when fetching the manifest. + */ + constructor(origin?: string, options = {}) { + const defaultOpts = { + version: false, + fullMetadata: false, + Arborist, + }; + + this.origin = origin; + this.installOptions = { ...defaultOpts, ...options }; + } + + /** + * Package name with version number. + * @type {string} + */ + get specifier() { + return ( + this.origin + + (this.installOptions.version ? "@" + this.installOptions.version : "") + ); + } + + /** + * Whether the plugin should be registered with its activation points. + * @type {boolean} + */ + get active() { + return this._active; + } + + /** + * Set Package details based on it's manifest + * @returns {Promise.} Resolves to true when the action completed + */ + async getManifest() { + // Get the package's manifest (package.json object) + try { + const mnf = await manifest(this.specifier, this.installOptions); + + // set the Package properties based on the it's manifest + this.name = mnf.name; + this.version = mnf.version; + this.activationPoints = mnf.activationPoints + ? (mnf.activationPoints as string[]) + : undefined; + this.main = mnf.main; + this.description = mnf.description; + this.icon = mnf.icon as any; + } catch (error) { + throw new Error( + `Package ${this.origin} does not contain a valid manifest: ${error}` + ); + } + + return true; + } + + /** + * Extract plugin to plugins folder. + * @returns {Promise.} This plugin + * @private + */ + async _install() { + try { + // import the manifest details + await this.getManifest(); + + // Install the package in a child folder of the given folder + await extract( + this.specifier, + join(pluginsPath ?? "", this.name ?? ""), + this.installOptions + ); + + if (!Array.isArray(this.activationPoints)) + throw new Error("The plugin does not contain any activation points"); + + // Set the url using the custom plugins protocol + this.url = `plugin://${this.name}/${this.main}`; + + this.emitUpdate(); + } catch (err) { + // Ensure the plugin is not stored and the folder is removed if the installation fails + this.setActive(false); + throw err; + } + + return [this]; + } + + /** + * Subscribe to updates of this plugin + * @param {string} name name of the callback to register + * @param {callback} cb The function to execute on update + */ + subscribe(name: string, cb: () => void) { + this.listeners[name] = cb; + } + + /** + * Remove subscription + * @param {string} name name of the callback to remove + */ + unsubscribe(name: string) { + delete this.listeners[name]; + } + + /** + * Execute listeners + */ + emitUpdate() { + for (const cb in this.listeners) { + this.listeners[cb].call(null, this); + } + } + + /** + * Check for updates and install if available. + * @param {string} version The version to update to. + * @returns {boolean} Whether an update was performed. + */ + async update(version = false) { + if (await this.isUpdateAvailable()) { + this.installOptions.version = version; + await this._install(); + return true; + } + + return false; + } + + /** + * Check if a new version of the plugin is available at the origin. + * @returns the latest available version if a new version is available or false if not. + */ + async isUpdateAvailable() { + if (this.origin) { + const mnf = await manifest(this.origin); + return mnf.version !== this.version ? mnf.version : false; + } + } + + /** + * Remove plugin and refresh renderers. + * @returns {Promise} + */ + async uninstall() { + const plgPath = resolve(pluginsPath ?? "", this.name ?? ""); + await rmdir(plgPath, { recursive: true }); + + this.emitUpdate(); + } + + /** + * Set a plugin's active state. This determines if a plugin should be loaded on initialisation. + * @param {boolean} active State to set _active to + * @returns {Plugin} This plugin + */ + setActive(active: boolean) { + this._active = active; + this.emitUpdate(); + return this; + } +} + +export default Plugin; diff --git a/server/core/plugin/router.ts b/server/core/plugin/router.ts new file mode 100644 index 000000000..09c79485b --- /dev/null +++ b/server/core/plugin/router.ts @@ -0,0 +1,97 @@ +import { ipcMain, webContents } from "electron"; + +import { + getPlugin, + getActivePlugins, + installPlugins, + removePlugin, + getAllPlugins, +} from "./store"; +import { pluginsPath } from "./globals"; +import Plugin from "./plugin"; + +// Throw an error if pluginsPath has not yet been provided by usePlugins. +const checkPluginsPath = () => { + if (!pluginsPath) + throw Error("Path to plugins folder has not yet been set up."); +}; +let active = false; +/** + * Provide the renderer process access to the plugins. + **/ +export default function () { + if (active) return; + // Register IPC route to install a plugin + ipcMain.handle("pluggable:install", async (e, plugins) => { + checkPluginsPath(); + + // Install and activate all provided plugins + const installed = await installPlugins(plugins); + return JSON.parse(JSON.stringify(installed)); + }); + + // Register IPC route to uninstall a plugin + ipcMain.handle("pluggable:uninstall", async (e, plugins, reload) => { + checkPluginsPath(); + + // Uninstall all provided plugins + for (const plg of plugins) { + const plugin = getPlugin(plg); + await plugin.uninstall(); + if (plugin.name) removePlugin(plugin.name); + } + + // Reload all renderer pages if needed + reload && webContents.getAllWebContents().forEach((wc) => wc.reload()); + return true; + }); + + // Register IPC route to update a plugin + ipcMain.handle("pluggable:update", async (e, plugins, reload) => { + checkPluginsPath(); + + // Update all provided plugins + const updated: Plugin[] = []; + for (const plg of plugins) { + const plugin = getPlugin(plg); + const res = await plugin.update(); + if (res) updated.push(plugin); + } + + // Reload all renderer pages if needed + if (updated.length && reload) + webContents.getAllWebContents().forEach((wc) => wc.reload()); + + return JSON.parse(JSON.stringify(updated)); + }); + + // Register IPC route to check if updates are available for a plugin + ipcMain.handle("pluggable:updatesAvailable", (e, names) => { + checkPluginsPath(); + + const plugins = names + ? names.map((name: string) => getPlugin(name)) + : getAllPlugins(); + + const updates: Record = {}; + for (const plugin of plugins) { + updates[plugin.name] = plugin.isUpdateAvailable(); + } + return updates; + }); + + // Register IPC route to get the list of active plugins + ipcMain.handle("pluggable:getActivePlugins", () => { + checkPluginsPath(); + return JSON.parse(JSON.stringify(getActivePlugins())); + }); + + // Register IPC route to toggle the active state of a plugin + ipcMain.handle("pluggable:togglePluginActive", (e, plg, active) => { + checkPluginsPath(); + const plugin = getPlugin(plg); + return JSON.parse(JSON.stringify(plugin.setActive(active))); + }); + + active = true; +} diff --git a/server/core/plugin/store.ts b/server/core/plugin/store.ts new file mode 100644 index 000000000..cfd25e5ca --- /dev/null +++ b/server/core/plugin/store.ts @@ -0,0 +1,131 @@ +/** + * Provides access to the plugins stored by Pluggable Electron + * @typedef {Object} pluginManager + * @prop {getPlugin} getPlugin + * @prop {getAllPlugins} getAllPlugins + * @prop {getActivePlugins} getActivePlugins + * @prop {installPlugins} installPlugins + * @prop {removePlugin} removePlugin + */ + +import { writeFileSync } from "fs"; +import Plugin from "./plugin"; +import { getPluginsFile } from "./globals"; + +/** + * @module store + * @private + */ + +/** + * Register of installed plugins + * @type {Object.} plugin - List of installed plugins + */ +const plugins: Record = {}; + +/** + * Get a plugin from the stored plugins. + * @param {string} name Name of the plugin to retrieve + * @returns {Plugin} Retrieved plugin + * @alias pluginManager.getPlugin + */ +export function getPlugin(name: string) { + if (!Object.prototype.hasOwnProperty.call(plugins, name)) { + throw new Error(`Plugin ${name} does not exist`); + } + + return plugins[name]; +} + +/** + * Get list of all plugin objects. + * @returns {Array.} All plugin objects + * @alias pluginManager.getAllPlugins + */ +export function getAllPlugins() { + return Object.values(plugins); +} + +/** + * Get list of active plugin objects. + * @returns {Array.} Active plugin objects + * @alias pluginManager.getActivePlugins + */ +export function getActivePlugins() { + return Object.values(plugins).filter((plugin) => plugin.active); +} + +/** + * Remove plugin from store and maybe save stored plugins to file + * @param {string} name Name of the plugin to remove + * @param {boolean} persist Whether to save the changes to plugins to file + * @returns {boolean} Whether the delete was successful + * @alias pluginManager.removePlugin + */ +export function removePlugin(name: string, persist = true) { + const del = delete plugins[name]; + if (persist) persistPlugins(); + return del; +} + +/** + * Add plugin to store and maybe save stored plugins to file + * @param {Plugin} plugin Plugin to add to store + * @param {boolean} persist Whether to save the changes to plugins to file + * @returns {void} + */ +export function addPlugin(plugin: Plugin, persist = true) { + if (plugin.name) plugins[plugin.name] = plugin; + if (persist) { + persistPlugins(); + plugin.subscribe("pe-persist", persistPlugins); + } +} + +/** + * Save stored plugins to file + * @returns {void} + */ +export function persistPlugins() { + const persistData: Record = {}; + for (const name in plugins) { + persistData[name] = plugins[name]; + } + writeFileSync(getPluginsFile(), JSON.stringify(persistData), "utf8"); +} + +/** + * Create and install a new plugin for the given specifier. + * @param {Array.} plugins A list of NPM specifiers, or installation configuration objects. + * @param {boolean} [store=true] Whether to store the installed plugins in the store + * @returns {Promise.>} New plugin + * @alias pluginManager.installPlugins + */ +export async function installPlugins(plugins: any, store = true) { + const installed: Plugin[] = []; + for (const plg of plugins) { + // Set install options and activation based on input type + const isObject = typeof plg === "object"; + const spec = isObject ? [plg.specifier, plg] : [plg]; + const activate = isObject ? plg.activate !== false : true; + + // Install and possibly activate plugin + const plugin = new Plugin(...spec); + await plugin._install(); + if (activate) plugin.setActive(true); + + // Add plugin to store if needed + if (store) addPlugin(plugin); + installed.push(plugin); + } + + // Return list of all installed plugins + return installed; +} + +/** + * @typedef {Object.} installOptions The {@link https://www.npmjs.com/package/pacote|pacote} + * options used to install the plugin with some extra options. + * @param {string} specifier the NPM specifier that identifies the package. + * @param {boolean} [activate] Whether this plugin should be activated after installation. Defaults to true. + */ diff --git a/server/core/pre-install/.gitkeep b/server/core/pre-install/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/handlers/download.ts b/server/handlers/download.ts new file mode 100644 index 000000000..3a1fc36d1 --- /dev/null +++ b/server/handlers/download.ts @@ -0,0 +1,108 @@ +import { app, ipcMain } from 'electron' +import { DownloadManager } from '../managers/download' +import { resolve, join } from 'path' +import { WindowManager } from '../managers/window' +import request from 'request' +import { createWriteStream, unlink } from 'fs' +const progress = require('request-progress') + +export function handleDownloaderIPCs() { + /** + * Handles the "pauseDownload" IPC message by pausing the download associated with the provided fileName. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle('pauseDownload', async (_event, fileName) => { + DownloadManager.instance.networkRequests[fileName]?.pause() + }) + + /** + * Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle('resumeDownload', async (_event, fileName) => { + DownloadManager.instance.networkRequests[fileName]?.resume() + }) + + /** + * Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName. + * The network request associated with the fileName is then removed from the networkRequests object. + * @param _event - The IPC event object. + * @param fileName - The name of the file being downloaded. + */ + ipcMain.handle('abortDownload', async (_event, fileName) => { + const rq = DownloadManager.instance.networkRequests[fileName] + DownloadManager.instance.networkRequests[fileName] = undefined + const userDataPath = app.getPath('userData') + const fullPath = join(userDataPath, fileName) + rq?.abort() + let result = 'NULL' + unlink(fullPath, function (err) { + if (err && err.code == 'ENOENT') { + result = `File not exist: ${err}` + } else if (err) { + result = `File delete error: ${err}` + } else { + result = 'File deleted successfully' + } + console.debug( + `Delete file ${fileName} from ${fullPath} result: ${result}` + ) + }) + }) + + /** + * Downloads a file from a given URL. + * @param _event - The IPC event object. + * @param url - The URL to download the file from. + * @param fileName - The name to give the downloaded file. + */ + ipcMain.handle('downloadFile', async (_event, url, fileName) => { + const userDataPath = join(app.getPath('home'), 'jan') + const destination = resolve(userDataPath, fileName) + const rq = request(url) + + progress(rq, {}) + .on('progress', function (state: any) { + WindowManager?.instance.currentWindow?.webContents.send( + 'FILE_DOWNLOAD_UPDATE', + { + ...state, + fileName, + } + ) + }) + .on('error', function (err: Error) { + WindowManager?.instance.currentWindow?.webContents.send( + 'FILE_DOWNLOAD_ERROR', + { + fileName, + err, + } + ) + }) + .on('end', function () { + if (DownloadManager.instance.networkRequests[fileName]) { + WindowManager?.instance.currentWindow?.webContents.send( + 'FILE_DOWNLOAD_COMPLETE', + { + fileName, + } + ) + DownloadManager.instance.setRequest(fileName, undefined) + } else { + WindowManager?.instance.currentWindow?.webContents.send( + 'FILE_DOWNLOAD_ERROR', + { + fileName, + err: 'Download cancelled', + } + ) + } + }) + .pipe(createWriteStream(destination)) + + DownloadManager.instance.setRequest(fileName, rq) + }) +} diff --git a/server/handlers/fs.ts b/server/handlers/fs.ts new file mode 100644 index 000000000..c1e8a85e4 --- /dev/null +++ b/server/handlers/fs.ts @@ -0,0 +1,156 @@ +import { app, ipcMain } from 'electron' +import * as fs from 'fs' +import { join } from 'path' + +/** + * Handles file system operations. + */ +export function handleFsIPCs() { + const userSpacePath = join(app.getPath('home'), 'jan') + + /** + * Gets the path to the user data directory. + * @param event - The event object. + * @returns A promise that resolves with the path to the user data directory. + */ + ipcMain.handle( + 'getUserSpace', + (): Promise => Promise.resolve(userSpacePath) + ) + + /** + * Checks whether the path is a directory. + * @param event - The event object. + * @param path - The path to check. + * @returns A promise that resolves with a boolean indicating whether the path is a directory. + */ + ipcMain.handle('isDirectory', (_event, path: string): Promise => { + const fullPath = join(userSpacePath, path) + return Promise.resolve( + fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() + ) + }) + + /** + * Reads a file from the user data directory. + * @param event - The event object. + * @param path - The path of the file to read. + * @returns A promise that resolves with the contents of the file. + */ + ipcMain.handle('readFile', async (event, path: string): Promise => { + return new Promise((resolve, reject) => { + fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => { + if (err) { + reject(err) + } else { + resolve(data) + } + }) + }) + }) + + /** + * Writes data to a file in the user data directory. + * @param event - The event object. + * @param path - The path of the file to write to. + * @param data - The data to write to the file. + * @returns A promise that resolves when the file has been written. + */ + ipcMain.handle( + 'writeFile', + async (event, path: string, data: string): Promise => { + return new Promise((resolve, reject) => { + fs.writeFile(join(userSpacePath, path), data, 'utf8', (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } + ) + + /** + * Creates a directory in the user data directory. + * @param event - The event object. + * @param path - The path of the directory to create. + * @returns A promise that resolves when the directory has been created. + */ + ipcMain.handle('mkdir', async (event, path: string): Promise => { + return new Promise((resolve, reject) => { + fs.mkdir(join(userSpacePath, path), { recursive: true }, (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + }) + + /** + * Removes a directory in the user data directory. + * @param event - The event object. + * @param path - The path of the directory to remove. + * @returns A promise that resolves when the directory is removed successfully. + */ + ipcMain.handle('rmdir', async (event, path: string): Promise => { + return new Promise((resolve, reject) => { + fs.rmdir(join(userSpacePath, path), { recursive: true }, (err) => { + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + }) + + /** + * Lists the files in a directory in the user data directory. + * @param event - The event object. + * @param path - The path of the directory to list files from. + * @returns A promise that resolves with an array of file names. + */ + ipcMain.handle( + 'listFiles', + async (event, path: string): Promise => { + return new Promise((resolve, reject) => { + fs.readdir(join(userSpacePath, path), (err, files) => { + if (err) { + reject(err) + } else { + resolve(files) + } + }) + }) + } + ) + + /** + * Deletes a file from the user data folder. + * @param _event - The IPC event object. + * @param filePath - The path to the file to delete. + * @returns A string indicating the result of the operation. + */ + ipcMain.handle('deleteFile', async (_event, filePath) => { + const fullPath = join(userSpacePath, filePath) + + let result = 'NULL' + fs.unlink(fullPath, function (err) { + if (err && err.code == 'ENOENT') { + result = `File not exist: ${err}` + } else if (err) { + result = `File delete error: ${err}` + } else { + result = 'File deleted successfully' + } + console.debug( + `Delete file ${filePath} from ${fullPath} result: ${result}` + ) + }) + + return result + }) +} diff --git a/server/handlers/plugin.ts b/server/handlers/plugin.ts new file mode 100644 index 000000000..22bf253e6 --- /dev/null +++ b/server/handlers/plugin.ts @@ -0,0 +1,118 @@ +import { app, ipcMain } from "electron"; +import { readdirSync, rmdir, writeFileSync } from "fs"; +import { ModuleManager } from "../managers/module"; +import { join, extname } from "path"; +import { PluginManager } from "../managers/plugin"; +import { WindowManager } from "../managers/window"; +import { manifest, tarball } from "pacote"; + +export function handlePluginIPCs() { + /** + * Invokes a function from a plugin module in main node process. + * @param _event - The IPC event object. + * @param modulePath - The path to the plugin module. + * @param method - The name of the function to invoke. + * @param args - The arguments to pass to the function. + * @returns The result of the invoked function. + */ + ipcMain.handle( + "invokePluginFunc", + async (_event, modulePath, method, ...args) => { + const module = require( + /* webpackIgnore: true */ join( + app.getPath("userData"), + "plugins", + modulePath + ) + ); + ModuleManager.instance.setModule(modulePath, module); + + if (typeof module[method] === "function") { + return module[method](...args); + } else { + console.debug(module[method]); + console.error(`Function "${method}" does not exist in the module.`); + } + } + ); + + /** + * Returns the paths of the base plugins. + * @param _event - The IPC event object. + * @returns An array of paths to the base plugins. + */ + ipcMain.handle("basePlugins", async (_event) => { + const basePluginPath = join( + __dirname, + "../", + app.isPackaged + ? "../../app.asar.unpacked/core/pre-install" + : "../core/pre-install" + ); + return readdirSync(basePluginPath) + .filter((file) => extname(file) === ".tgz") + .map((file) => join(basePluginPath, file)); + }); + + /** + * Returns the path to the user's plugin directory. + * @param _event - The IPC event object. + * @returns The path to the user's plugin directory. + */ + ipcMain.handle("pluginPath", async (_event) => { + return join(app.getPath("userData"), "plugins"); + }); + + /** + * Deletes the `plugins` directory in the user data path and disposes of required modules. + * If the app is packaged, the function relaunches the app and exits. + * Otherwise, the function deletes the cached modules and sets up the plugins and reloads the main window. + * @param _event - The IPC event object. + * @param url - The URL to reload. + */ + ipcMain.handle("reloadPlugins", async (_event, url) => { + const userDataPath = app.getPath("userData"); + const fullPath = join(userDataPath, "plugins"); + + rmdir(fullPath, { recursive: true }, function (err) { + if (err) console.error(err); + ModuleManager.instance.clearImportedModules(); + + // just relaunch if packaged, should launch manually in development mode + if (app.isPackaged) { + app.relaunch(); + app.exit(); + } else { + for (const modulePath in ModuleManager.instance.requiredModules) { + delete require.cache[ + require.resolve( + join(app.getPath("userData"), "plugins", modulePath) + ) + ]; + } + PluginManager.instance.setupPlugins(); + WindowManager.instance.currentWindow?.reload(); + } + }); + }); + + /** + * Installs a remote plugin by downloading its tarball and writing it to a tgz file. + * @param _event - The IPC event object. + * @param pluginName - The name of the remote plugin to install. + * @returns A Promise that resolves to the path of the installed plugin file. + */ + ipcMain.handle("installRemotePlugin", async (_event, pluginName) => { + const destination = join( + app.getPath("userData"), + pluginName.replace(/^@.*\//, "") + ".tgz" + ); + return manifest(pluginName) + .then(async (manifest: any) => { + await tarball(manifest._resolved).then((data: Buffer) => { + writeFileSync(destination, data); + }); + }) + .then(() => destination); + }); +} diff --git a/server/icons/icon.png b/server/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..289f99ded85e1e3cc91b17af6b607d2cafd97cd7 GIT binary patch literal 38651 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 + PluginManager.instance.migratePlugins() + PluginManager.instance.setupPlugins() + setupMenu() + handleIPCs() +}) + +/** + * Handles various IPC messages from the renderer process. + */ +function handleIPCs() { + handleFsIPCs() + handleDownloaderIPCs() + handlePluginIPCs() +} diff --git a/server/managers/download.ts b/server/managers/download.ts new file mode 100644 index 000000000..08c089b74 --- /dev/null +++ b/server/managers/download.ts @@ -0,0 +1,24 @@ +import { Request } from "request"; + +/** + * Manages file downloads and network requests. + */ +export class DownloadManager { + public networkRequests: Record = {}; + + public static instance: DownloadManager = new DownloadManager(); + + constructor() { + if (DownloadManager.instance) { + return DownloadManager.instance; + } + } + /** + * Sets a network request for a specific file. + * @param {string} fileName - The name of the file. + * @param {Request | undefined} request - The network request to set, or undefined to clear the request. + */ + setRequest(fileName: string, request: Request | undefined) { + this.networkRequests[fileName] = request; + } +} diff --git a/server/managers/module.ts b/server/managers/module.ts new file mode 100644 index 000000000..43dda0fb6 --- /dev/null +++ b/server/managers/module.ts @@ -0,0 +1,33 @@ +import { dispose } from "../utils/disposable"; + +/** + * Manages imported modules. + */ +export class ModuleManager { + public requiredModules: Record = {}; + + public static instance: ModuleManager = new ModuleManager(); + + constructor() { + if (ModuleManager.instance) { + return ModuleManager.instance; + } + } + + /** + * Sets a module. + * @param {string} moduleName - The name of the module. + * @param {any | undefined} nodule - The module to set, or undefined to clear the module. + */ + setModule(moduleName: string, nodule: any | undefined) { + this.requiredModules[moduleName] = nodule; + } + + /** + * Clears all imported modules. + */ + clearImportedModules() { + dispose(this.requiredModules); + this.requiredModules = {}; + } +} diff --git a/server/managers/plugin.ts b/server/managers/plugin.ts new file mode 100644 index 000000000..227eab34e --- /dev/null +++ b/server/managers/plugin.ts @@ -0,0 +1,60 @@ +import { app } from "electron"; +import { init } from "../core/plugin/index"; +import { join } from "path"; +import { rmdir } from "fs"; +import Store from "electron-store"; + +/** + * Manages plugin installation and migration. + */ +export class PluginManager { + public static instance: PluginManager = new PluginManager(); + + constructor() { + if (PluginManager.instance) { + return PluginManager.instance; + } + } + + /** + * Sets up the plugins by initializing the `plugins` module with the `confirmInstall` and `pluginsPath` options. + * The `confirmInstall` function always returns `true` to allow plugin installation. + * The `pluginsPath` option specifies the path to install plugins to. + */ + setupPlugins() { + init({ + // Function to check from the main process that user wants to install a plugin + confirmInstall: async (_plugins: string[]) => { + return true; + }, + // Path to install plugin to + pluginsPath: join(app.getPath("userData"), "plugins"), + }); + } + + /** + * Migrates the plugins by deleting the `plugins` directory in the user data path. + * If the `migrated_version` key in the `Store` object does not match the current app version, + * the function deletes the `plugins` directory and sets the `migrated_version` key to the current app version. + * @returns A Promise that resolves when the migration is complete. + */ + migratePlugins() { + return new Promise((resolve) => { + const store = new Store(); + if (store.get("migrated_version") !== app.getVersion()) { + console.debug("start migration:", store.get("migrated_version")); + const userDataPath = app.getPath("userData"); + const fullPath = join(userDataPath, "plugins"); + + rmdir(fullPath, { recursive: true }, function (err) { + if (err) console.error(err); + store.set("migrated_version", app.getVersion()); + console.debug("migrate plugins done"); + resolve(undefined); + }); + } else { + resolve(undefined); + } + }); + } +} diff --git a/server/managers/window.ts b/server/managers/window.ts new file mode 100644 index 000000000..c930dd5ec --- /dev/null +++ b/server/managers/window.ts @@ -0,0 +1,37 @@ +import { BrowserWindow } from "electron"; + +/** + * Manages the current window instance. + */ +export class WindowManager { + public static instance: WindowManager = new WindowManager(); + public currentWindow?: BrowserWindow; + + constructor() { + if (WindowManager.instance) { + return WindowManager.instance; + } + } + + /** + * Creates a new window instance. + * @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with. + * @returns The created window instance. + */ + createWindow(options?: Electron.BrowserWindowConstructorOptions | undefined) { + this.currentWindow = new BrowserWindow({ + width: 1200, + minWidth: 800, + height: 800, + show: false, + trafficLightPosition: { + x: 10, + y: 15, + }, + titleBarStyle: "hidden", + vibrancy: "sidebar", + ...options, + }); + return this.currentWindow; + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 000000000..8185f37d9 --- /dev/null +++ b/server/package.json @@ -0,0 +1,97 @@ +{ + "name": "jan-server", + "version": "0.1.3", + "main": "./build/main.js", + "author": "Jan ", + "license": "MIT", + "homepage": "https://github.com/janhq/jan/tree/main/electron", + "description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.", + "build": { + "appId": "jan.ai.app", + "productName": "Jan", + "files": [ + "renderer/**/*", + "build/*.{js,map}", + "build/**/*.{js,map}", + "core/pre-install", + "core/plugin-manager/facade" + ], + "asarUnpack": [ + "core/pre-install" + ], + "publish": [ + { + "provider": "github", + "owner": "janhq", + "repo": "jan" + } + ], + "extends": null, + "mac": { + "type": "distribution", + "entitlements": "./entitlements.mac.plist", + "entitlementsInherit": "./entitlements.mac.plist", + "notarize": { + "teamId": "YT49P7GXG4" + }, + "icon": "icons/icon.png" + }, + "linux": { + "target": [ + "deb" + ], + "category": "Utility", + "icon": "icons/" + }, + "win": { + "icon": "icons/icon.png" + }, + "artifactName": "jan-${os}-${arch}-${version}.${ext}" + }, + "scripts": { + "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", + "test:e2e": "playwright test --workers=1", + "dev": "tsc -p . && electron .", + "build": "run-script-os", + "build:test": "run-script-os", + "build:test:darwin": "tsc -p . && electron-builder -p never -m --dir", + "build:test:win32": "tsc -p . && electron-builder -p never -w --dir", + "build:test:linux": "tsc -p . && electron-builder -p never -l --dir", + "build:darwin": "tsc -p . && electron-builder -p never -m", + "build:win32": "tsc -p . && electron-builder -p never -w", + "build:linux": "tsc -p . && electron-builder -p never --linux deb", + "build:publish": "run-script-os", + "build:publish:darwin": "tsc -p . && electron-builder -p onTagOrDraft -m --x64 --arm64", + "build:publish:win32": "tsc -p . && electron-builder -p onTagOrDraft -w", + "build:publish:linux": "tsc -p . && electron-builder -p onTagOrDraft -l deb" + }, + "dependencies": { + "@npmcli/arborist": "^7.1.0", + "@types/express": "^4.17.21", + "@types/request": "^2.48.12", + "@uiball/loaders": "^1.3.0", + "electron-store": "^8.1.0", + "electron-updater": "^6.1.4", + "express": "^4.18.2", + "pacote": "^17.0.4", + "request": "^2.88.2", + "request-progress": "^3.0.0", + "use-debounce": "^9.0.4" + }, + "devDependencies": { + "@electron/notarize": "^2.1.0", + "@playwright/test": "^1.38.1", + "@types/npmcli__arborist": "^5.6.4", + "@types/pacote": "^11.1.7", + "@typescript-eslint/eslint-plugin": "^6.7.3", + "@typescript-eslint/parser": "^6.7.3", + "electron": "26.2.1", + "electron-builder": "^24.6.4", + "electron-playwright-helpers": "^1.6.0", + "eslint-plugin-react": "^7.33.2", + "run-script-os": "^1.1.6" + }, + "installConfig": { + "hoistingLimits": "workspaces" + } +} diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 000000000..3cc218f93 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "commonjs", + "noImplicitAny": true, + "sourceMap": true, + "strict": true, + "outDir": "./build", + "rootDir": "./", + "noEmitOnError": true, + "esModuleInterop": true, + "baseUrl": ".", + "allowJs": true, + "skipLibCheck": true, + "paths": { "*": ["node_modules/*"] }, + "typeRoots": ["node_modules/@types"] + }, + "include": ["./**/*.ts"], + "exclude": ["core", "build", "dist", "tests", "node_modules"] +} diff --git a/server/utils/disposable.ts b/server/utils/disposable.ts new file mode 100644 index 000000000..462f7e3e5 --- /dev/null +++ b/server/utils/disposable.ts @@ -0,0 +1,8 @@ +export function dispose(requiredModules: Record) { + for (const key in requiredModules) { + const module = requiredModules[key]; + if (typeof module["dispose"] === "function") { + module["dispose"](); + } + } +} diff --git a/server/utils/menu.ts b/server/utils/menu.ts new file mode 100644 index 000000000..65e009aef --- /dev/null +++ b/server/utils/menu.ts @@ -0,0 +1,111 @@ +// @ts-nocheck +const { app, Menu, dialog } = require("electron"); +const isMac = process.platform === "darwin"; +const { autoUpdater } = require("electron-updater"); +import { compareSemanticVersions } from "./versionDiff"; + +const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [ + ...(isMac + ? [ + { + label: app.name, + submenu: [ + { role: "about" }, + { + label: "Check for Updates...", + click: () => + autoUpdater.checkForUpdatesAndNotify().then((e) => { + if ( + !e || + compareSemanticVersions( + app.getVersion(), + e.updateInfo.version + ) >= 0 + ) + dialog.showMessageBox({ + message: `There are currently no updates available.`, + }); + }), + }, + { type: "separator" }, + { role: "services" }, + { type: "separator" }, + { role: "hide" }, + { role: "hideOthers" }, + { role: "unhide" }, + { type: "separator" }, + { role: "quit" }, + ], + }, + ] + : []), + { + label: "Edit", + submenu: [ + { role: "undo" }, + { role: "redo" }, + { type: "separator" }, + { role: "cut" }, + { role: "copy" }, + { role: "paste" }, + ...(isMac + ? [ + { role: "pasteAndMatchStyle" }, + { role: "delete" }, + { role: "selectAll" }, + { type: "separator" }, + { + label: "Speech", + submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }], + }, + ] + : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), + ], + }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { role: "toggleDevTools" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { + label: "Window", + submenu: [ + { role: "minimize" }, + { role: "zoom" }, + ...(isMac + ? [ + { type: "separator" }, + { role: "front" }, + { type: "separator" }, + { role: "window" }, + ] + : [{ role: "close" }]), + ], + }, + { + role: "help", + submenu: [ + { + label: "Learn More", + click: async () => { + const { shell } = require("electron"); + await shell.openExternal("https://jan.ai/"); + }, + }, + ], + }, +]; + +export const setupMenu = () => { + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +}; diff --git a/server/utils/versionDiff.ts b/server/utils/versionDiff.ts new file mode 100644 index 000000000..25934e87f --- /dev/null +++ b/server/utils/versionDiff.ts @@ -0,0 +1,21 @@ +export const compareSemanticVersions = (a: string, b: string) => { + + // 1. Split the strings into their parts. + const a1 = a.split('.'); + const b1 = b.split('.'); + // 2. Contingency in case there's a 4th or 5th version + const len = Math.min(a1.length, b1.length); + // 3. Look through each version number and compare. + for (let i = 0; i < len; i++) { + const a2 = +a1[ i ] || 0; + const b2 = +b1[ i ] || 0; + + if (a2 !== b2) { + return a2 > b2 ? 1 : -1; + } + } + + // 4. We hit this if the all checked versions so far are equal + // + return b1.length - a1.length; +}; \ No newline at end of file From 64a58d1e15242402caaa1bd0aff2e2cc5b312732 Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Wed, 29 Nov 2023 09:29:04 +0700 Subject: [PATCH 002/179] WIP: fs adapter calling server API --- Makefile | 2 +- core/src/fs.ts | 143 +++++++++++++++++++++++++++++++++++++++----- server/main.ts | 35 ++++++----- server/package.json | 3 +- 4 files changed, 147 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index af416304c..e0f6cb274 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ install-and-build: build-uikit ifeq ($(OS),Windows_NT) powershell -Command "yarn config set network-timeout 300000; \ $$env:NITRO_VERSION = Get-Content .\\plugins\\inference-plugin\\nitro\\version.txt; \ - Write-Output \"Nitro version: $$env:NITRO_VERSION\"; yarn build:core; yarn install; yarn build:plugins" + Write-Output \"Nitro version: $$env:NITRO_VERSION\"; yarn build:core; yarn install; yarn build:plugins" else yarn build:core yarn install diff --git a/core/src/fs.ts b/core/src/fs.ts index 2c94a2ce8..b8722e1a6 100644 --- a/core/src/fs.ts +++ b/core/src/fs.ts @@ -1,59 +1,170 @@ +const fetchRetry = require("fetch-retry")(global.fetch); + +const PORT = 1337; +const LOCAL_HOST = "127.0.0.1"; +const JAN_HTTP_SERVER_URL = `http://${LOCAL_HOST}:${PORT}`; +const JAN_FS_API = `${JAN_HTTP_SERVER_URL}/fs`; /** * Writes data to a file at the specified path. * @param {string} path - The path to the file. * @param {string} data - The data to write to the file. * @returns {Promise} A Promise that resolves when the file is written successfully. */ -const writeFile: (path: string, data: string) => Promise = (path, data) => - window.coreAPI?.writeFile(path, data) ?? - window.electronAPI?.writeFile(path, data); - +const writeFile = (path: string, data: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'writeFile', + path, + data + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`writeFile: ${path} failed`); + }) +} + /** * Checks whether the path is a directory. * @param path - The path to check. * @returns {boolean} A boolean indicating whether the path is a directory. */ -const isDirectory = (path: string): Promise => - window.coreAPI?.isDirectory(path) ?? window.electronAPI?.isDirectory(path); +const isDirectory = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'isDirectory', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`isDirectory: ${path} failed`); + }) +} /** * Reads the contents of a file at the specified path. * @param {string} path - The path of the file to read. * @returns {Promise} A Promise that resolves with the contents of the file. */ -const readFile: (path: string) => Promise = (path) => - window.coreAPI?.readFile(path) ?? window.electronAPI?.readFile(path); +const readFile = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'readFile', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`readFile: ${path} failed`); + }) +} /** * List the directory files * @param {string} path - The path of the directory to list files. * @returns {Promise} A Promise that resolves with the contents of the directory. */ -const listFiles: (path: string) => Promise = (path) => - window.coreAPI?.listFiles(path) ?? window.electronAPI?.listFiles(path); +const listFiles = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'listFiles', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`listFiles: ${path} failed`); + }) +} /** * Creates a directory at the specified path. * @param {string} path - The path of the directory to create. * @returns {Promise} A Promise that resolves when the directory is created successfully. */ -const mkdir: (path: string) => Promise = (path) => - window.coreAPI?.mkdir(path) ?? window.electronAPI?.mkdir(path); +const mkdir = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'mkdir', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`mkdir: ${path} failed`); + }) +} /** * Removes a directory at the specified path. * @param {string} path - The path of the directory to remove. * @returns {Promise} A Promise that resolves when the directory is removed successfully. */ -const rmdir: (path: string) => Promise = (path) => - window.coreAPI?.rmdir(path) ?? window.electronAPI?.rmdir(path); +const rmdir = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'rmdir', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`rmdir: ${path} failed`); + }) +} /** * Deletes a file from the local file system. * @param {string} path - The path of the file to delete. * @returns {Promise} A Promise that resolves when the file is deleted. */ -const deleteFile: (path: string) => Promise = (path) => - window.coreAPI?.deleteFile(path) ?? window.electronAPI?.deleteFile(path); +const deleteFile = (path: string): Promise => { + return fetchRetry(JAN_FS_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + op: 'deleteFile', + path, + }), + retries: 3, + retryDelay: 500, + }).catch((err: any) => { + console.error(err); + throw new Error(`deleteFile: ${path} failed`); + }) +} export const fs = { isDirectory, diff --git a/server/main.ts b/server/main.ts index a4f4df1bb..5ba2045c1 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,32 +1,31 @@ -import { join } from 'path' import { setupMenu } from './utils/menu' -import { handleFsIPCs } from './handlers/fs' import app from 'express' - +import bodyParser from 'body-parser' +import fs from 'fs' /** * Managers **/ import { ModuleManager } from './managers/module' import { PluginManager } from './managers/plugin' -/** - * IPC Handlers - **/ -import { handleDownloaderIPCs } from './handlers/download' -import { handlePluginIPCs } from './handlers/plugin' -app().listen(6969, ()=>{ +const server = app() +server.use(bodyParser) + +const USER_ROOT_DIR = '.data' +server.post("fs", (req, res) => { + let op = req.body.op; + switch(op){ + case 'readFile': + fs.readFile(req.body.path, ()=>{}) + case 'writeFile': + fs.writeFile(req.body.path, Buffer.from(req.body.data, "base64"), ()=>{}) + } +}) + +server.listen(1337, ()=>{ PluginManager.instance.migratePlugins() PluginManager.instance.setupPlugins() setupMenu() - handleIPCs() }) -/** - * Handles various IPC messages from the renderer process. - */ -function handleIPCs() { - handleFsIPCs() - handleDownloaderIPCs() - handlePluginIPCs() -} diff --git a/server/package.json b/server/package.json index 8185f37d9..9523fa000 100644 --- a/server/package.json +++ b/server/package.json @@ -67,7 +67,6 @@ }, "dependencies": { "@npmcli/arborist": "^7.1.0", - "@types/express": "^4.17.21", "@types/request": "^2.48.12", "@uiball/loaders": "^1.3.0", "electron-store": "^8.1.0", @@ -81,6 +80,8 @@ "devDependencies": { "@electron/notarize": "^2.1.0", "@playwright/test": "^1.38.1", + "@types/body-parser": "^1.19.5", + "@types/express": "^4.17.21", "@types/npmcli__arborist": "^5.6.4", "@types/pacote": "^11.1.7", "@typescript-eslint/eslint-plugin": "^6.7.3", From 69bdfccddb7e9fa7f1c9c91911419975449eab56 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:15:19 +0900 Subject: [PATCH 003/179] docs: add assistants endpoint --- docs/openapi/jan.yaml | 13 +++++++ docs/openapi/specs/assistant.yaml | 59 ----------------------------- docs/openapi/specs/assistants.yaml | 61 ++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 59 deletions(-) delete mode 100644 docs/openapi/specs/assistant.yaml create mode 100644 docs/openapi/specs/assistants.yaml diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index 43c07cb3c..fd2a6fe44 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -709,6 +709,19 @@ x-webhooks: application/json: schema: $ref: 'specs/models.yaml#/components/schemas/ModelObject' + AssistantObject: + post: + summary: The assistant object + description: | + Build assistants that can call models and use tools to perform tasks. Equivalent to OpenAI's assistants object. + operationId: AssistantObject + tags: + - Assistants + requestBody: + content: + application/json: + schema: + $ref: 'specs/assistants.yaml#/components/schemas/AssistantObject' MessageObject: post: summary: The message object diff --git a/docs/openapi/specs/assistant.yaml b/docs/openapi/specs/assistant.yaml deleted file mode 100644 index 95b4cd22d..000000000 --- a/docs/openapi/specs/assistant.yaml +++ /dev/null @@ -1,59 +0,0 @@ -AssistantObject: - type: object - properties: - avatar: - type: string - description: "URL of the assistant's avatar. Jan-specific property." - example: "https://lala.png" - id: - type: string - description: "The identifier of the assistant." - example: "asst_abc123" - object: - type: string - description: "Type of the object, indicating it's an assistant." - default: "assistant" - version: - type: integer - description: "Version number of the assistant." - example: 1 - created_at: - type: integer - format: int64 - description: "Unix timestamp representing the creation time of the assistant." - name: - type: string - description: "Name of the assistant." - example: "Math Tutor" - description: - type: string - description: "Description of the assistant. Can be null." - models: - type: array - description: "List of models associated with the assistant. Jan-specific property." - items: - type: object - properties: - model_id: - type: string - # Additional properties for models can be added here - events: - type: object - description: "Event subscription settings for the assistant." - properties: - in: - type: array - items: - type: string - out: - type: array - items: - type: string - # If there are specific event types, they can be detailed here - metadata: - type: object - description: "Metadata associated with the assistant." - required: - - name - - models - - events \ No newline at end of file diff --git a/docs/openapi/specs/assistants.yaml b/docs/openapi/specs/assistants.yaml new file mode 100644 index 000000000..bb9c96455 --- /dev/null +++ b/docs/openapi/specs/assistants.yaml @@ -0,0 +1,61 @@ +components: + schemas: + AssistantObject: + type: object + properties: + avatar: + type: string + description: "URL of the assistant's avatar. Jan-specific property." + example: "https://lala.png" + id: + type: string + description: "The identifier of the assistant." + example: "asst_abc123" + object: + type: string + description: "Type of the object, indicating it's an assistant." + default: "assistant" + version: + type: integer + description: "Version number of the assistant." + example: 1 + created_at: + type: integer + format: int64 + description: "Unix timestamp representing the creation time of the assistant." + name: + type: string + description: "Name of the assistant." + example: "Math Tutor" + description: + type: string + description: "Description of the assistant. Can be null." + models: + type: array + description: "List of models associated with the assistant. Jan-specific property." + items: + type: object + properties: + model_id: + type: string + # Additional properties for models can be added here + events: + type: object + description: "Event subscription settings for the assistant." + properties: + in: + type: array + items: + type: string + out: + type: array + items: + type: string + # If there are specific event types, they can be detailed here + metadata: + type: object + description: "Metadata associated with the assistant." + required: + - name + - models + - events \ No newline at end of file From 2187408604eaf2cd2b27033c650f85f6a373036f Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:30:33 +0900 Subject: [PATCH 004/179] docs: add assistant endpoint --- docs/openapi/jan.yaml | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index fd2a6fe44..ee646f1eb 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -476,7 +476,46 @@ paths: - lang: "curl" source: | curl http://localhost:1337/v1/threads/{thread_id} + ### ASSISTANTS + /assistants/: + get: + operationId: listAssistants + tags: + - Assistants + summary: List assistants + description: | + Return a list of assistants. Equivalent to OpenAI's list assistants. + post: + operationId: createAssistants + tags: + - Assistants + summary: Create assistant + description: | + Create an assistant with a model and instructions. Equivalent to OpenAI's create assistants. + /assistants/{assistant_id}: + get: + operationId: getAssistant + tags: + - Assistants + summary: Retrieve assistants + description: | + Retrieves an assistant. Equivalent to OpenAI's retrieve assistants. + post: + operationId: modifyAssistant + tags: + - Assistants + summary: Modify assistant + description: | + Modifies an assistant. Equivalent to OpenAI's modify assistant. + delete: + operationId: deleteAssistant + tags: + - Assistants + summary: Delete assistant + description: | + Delete an assistant. Equivalent to OpenAI's delete assistant. + ### MESSAGES /threads/{thread_id}/messages: get: @@ -714,7 +753,7 @@ x-webhooks: summary: The assistant object description: | Build assistants that can call models and use tools to perform tasks. Equivalent to OpenAI's assistants object. - operationId: AssistantObject + operationId: AssistantObjects tags: - Assistants requestBody: From a5446b038dc85308120d0d5c6a4e9bf92030c282 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:45:45 +0900 Subject: [PATCH 005/179] docs: fix endpoint url --- docs/openapi/jan.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index ee646f1eb..8f5ee08b2 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -486,7 +486,7 @@ paths: description: | Return a list of assistants. Equivalent to OpenAI's list assistants. post: - operationId: createAssistants + operationId: createAssistant tags: - Assistants summary: Create assistant @@ -498,7 +498,7 @@ paths: operationId: getAssistant tags: - Assistants - summary: Retrieve assistants + summary: Retrieve assistant description: | Retrieves an assistant. Equivalent to OpenAI's retrieve assistants. post: From 198cf2e2fcf9f370959f3c654c40084baf62dc20 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Fri, 1 Dec 2023 11:24:27 +0900 Subject: [PATCH 006/179] docs: add response structure --- docs/openapi/jan.yaml | 31 ++++++++++- docs/openapi/specs/assistants.yaml | 82 +++++++++++++++++++++++++++++- 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/docs/openapi/jan.yaml b/docs/openapi/jan.yaml index 8f5ee08b2..ccf54f2ce 100644 --- a/docs/openapi/jan.yaml +++ b/docs/openapi/jan.yaml @@ -485,6 +485,13 @@ paths: summary: List assistants description: | Return a list of assistants. Equivalent to OpenAI's list assistants. + responses: + "200": + description: + content: + application/json: + schema: + $ref: "specs/assistants.yaml#/components/schemas/ListAssistantsResponse" post: operationId: createAssistant tags: @@ -492,6 +499,13 @@ paths: summary: Create assistant description: | Create an assistant with a model and instructions. Equivalent to OpenAI's create assistants. + responses: + "200": + description: + content: + application/json: + schema: + $ref: "specs/assistants.yaml#/components/schemas/CreateAssistantResponse" /assistants/{assistant_id}: get: @@ -501,6 +515,14 @@ paths: summary: Retrieve assistant description: | Retrieves an assistant. Equivalent to OpenAI's retrieve assistants. + responses: + "200": + description: + content: + application/json: + schema: + $ref: "specs/assistants.yaml#/components/schemas/RetrieveAssistantResponse" + post: operationId: modifyAssistant tags: @@ -515,7 +537,14 @@ paths: summary: Delete assistant description: | Delete an assistant. Equivalent to OpenAI's delete assistant. - + responses: + "200": + description: + content: + application/json: + schema: + $ref: "specs/assistants.yaml#/components/schemas/ModifyAssistantResponse" + ### MESSAGES /threads/{thread_id}/messages: get: diff --git a/docs/openapi/specs/assistants.yaml b/docs/openapi/specs/assistants.yaml index bb9c96455..9db27e308 100644 --- a/docs/openapi/specs/assistants.yaml +++ b/docs/openapi/specs/assistants.yaml @@ -58,4 +58,84 @@ components: required: - name - models - - events \ No newline at end of file + - events + + ListAssistantsResponse: + type: object + properties: + id: + type: string + description: "The identifier of the model that was deleted." + example: "model-zephyr-7B" + object: + type: string + description: "Type of the object, indicating it's a model." + default: "model" + deleted: + type: boolean + description: "Indicates whether the model was successfully deleted." + example: true + + CreateAssistantResponse: + type: object + properties: + id: + type: string + description: "The identifier of the model that was deleted." + example: "model-zephyr-7B" + object: + type: string + description: "Type of the object, indicating it's a model." + default: "model" + deleted: + type: boolean + description: "Indicates whether the model was successfully deleted." + example: true + + RetrieveAssistantResponse: + type: object + properties: + id: + type: string + description: "The identifier of the model that was deleted." + example: "model-zephyr-7B" + object: + type: string + description: "Type of the object, indicating it's a model." + default: "model" + deleted: + type: boolean + description: "Indicates whether the model was successfully deleted." + example: true + + ModifyAssistantResponse: + type: object + properties: + id: + type: string + description: "The identifier of the model that was deleted." + example: "model-zephyr-7B" + object: + type: string + description: "Type of the object, indicating it's a model." + default: "model" + deleted: + type: boolean + description: "Indicates whether the model was successfully deleted." + example: true + + DeleteAssistantResponse: + type: object + properties: + id: + type: string + description: "The identifier of the model that was deleted." + example: "model-zephyr-7B" + object: + type: string + description: "Type of the object, indicating it's a model." + default: "model" + deleted: + type: boolean + description: "Indicates whether the model was successfully deleted." + example: true \ No newline at end of file From e5f57b853bd334f724ef059b138fedd47228f04c Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Fri, 1 Dec 2023 09:35:51 +0700 Subject: [PATCH 007/179] boilerplate for express server --- server/core/plugin/facade.ts | 30 --- server/core/plugin/globals.ts | 36 ---- server/core/plugin/index.ts | 149 -------------- server/core/plugin/plugin.ts | 213 --------------------- server/core/plugin/router.ts | 97 ---------- server/core/plugin/store.ts | 131 ------------- server/{core/pre-install => data}/.gitkeep | 0 server/handlers/download.ts | 108 ----------- server/handlers/fs.ts | 156 --------------- server/handlers/plugin.ts | 118 ------------ server/lib/.gitkeep | 0 server/main.ts | 43 ++--- server/managers/download.ts | 24 --- server/managers/module.ts | 33 ---- server/managers/plugin.ts | 60 ------ server/managers/window.ts | 37 ---- server/nodemon.json | 5 + server/package.json | 24 +-- server/tsconfig.json | 2 + server/utils/disposable.ts | 8 - server/utils/menu.ts | 111 ----------- server/utils/versionDiff.ts | 21 -- server/v1/assistants/index.ts | 5 + server/v1/chat/index.ts | 5 + server/v1/index.ts | 20 ++ server/v1/models/downloadModel.ts | 5 + server/v1/models/index.ts | 18 ++ server/v1/threads/index.ts | 5 + 28 files changed, 90 insertions(+), 1374 deletions(-) delete mode 100644 server/core/plugin/facade.ts delete mode 100644 server/core/plugin/globals.ts delete mode 100644 server/core/plugin/index.ts delete mode 100644 server/core/plugin/plugin.ts delete mode 100644 server/core/plugin/router.ts delete mode 100644 server/core/plugin/store.ts rename server/{core/pre-install => data}/.gitkeep (100%) delete mode 100644 server/handlers/download.ts delete mode 100644 server/handlers/fs.ts delete mode 100644 server/handlers/plugin.ts create mode 100644 server/lib/.gitkeep delete mode 100644 server/managers/download.ts delete mode 100644 server/managers/module.ts delete mode 100644 server/managers/plugin.ts delete mode 100644 server/managers/window.ts create mode 100644 server/nodemon.json delete mode 100644 server/utils/disposable.ts delete mode 100644 server/utils/menu.ts delete mode 100644 server/utils/versionDiff.ts create mode 100644 server/v1/assistants/index.ts create mode 100644 server/v1/chat/index.ts create mode 100644 server/v1/index.ts create mode 100644 server/v1/models/downloadModel.ts create mode 100644 server/v1/models/index.ts create mode 100644 server/v1/threads/index.ts diff --git a/server/core/plugin/facade.ts b/server/core/plugin/facade.ts deleted file mode 100644 index bd1089109..000000000 --- a/server/core/plugin/facade.ts +++ /dev/null @@ -1,30 +0,0 @@ -const { ipcRenderer, contextBridge } = require("electron"); - -export function useFacade() { - const interfaces = { - install(plugins: any[]) { - return ipcRenderer.invoke("pluggable:install", plugins); - }, - uninstall(plugins: any[], reload: boolean) { - return ipcRenderer.invoke("pluggable:uninstall", plugins, reload); - }, - getActive() { - return ipcRenderer.invoke("pluggable:getActivePlugins"); - }, - update(plugins: any[], reload: boolean) { - return ipcRenderer.invoke("pluggable:update", plugins, reload); - }, - updatesAvailable(plugin: any) { - return ipcRenderer.invoke("pluggable:updatesAvailable", plugin); - }, - toggleActive(plugin: any, active: boolean) { - return ipcRenderer.invoke("pluggable:togglePluginActive", plugin, active); - }, - }; - - if (contextBridge) { - contextBridge.exposeInMainWorld("pluggableElectronIpc", interfaces); - } - - return interfaces; -} diff --git a/server/core/plugin/globals.ts b/server/core/plugin/globals.ts deleted file mode 100644 index 69df7925c..000000000 --- a/server/core/plugin/globals.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { existsSync, mkdirSync, writeFileSync } from "fs"; -import { join, resolve } from "path"; - -export let pluginsPath: string | undefined = undefined; - -/** - * @private - * Set path to plugins directory and create the directory if it does not exist. - * @param {string} plgPath path to plugins directory - */ -export function setPluginsPath(plgPath: string) { - // Create folder if it does not exist - let plgDir; - try { - plgDir = resolve(plgPath); - if (plgDir.length < 2) throw new Error(); - - if (!existsSync(plgDir)) mkdirSync(plgDir); - - const pluginsJson = join(plgDir, "plugins.json"); - if (!existsSync(pluginsJson)) writeFileSync(pluginsJson, "{}", "utf8"); - - pluginsPath = plgDir; - } catch (error) { - throw new Error("Invalid path provided to the plugins folder"); - } -} - -/** - * @private - * Get the path to the plugins.json file. - * @returns location of plugins.json - */ -export function getPluginsFile() { - return join(pluginsPath ?? "", "plugins.json"); -} \ No newline at end of file diff --git a/server/core/plugin/index.ts b/server/core/plugin/index.ts deleted file mode 100644 index e8c64747b..000000000 --- a/server/core/plugin/index.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { readFileSync } from "fs"; -import { protocol } from "electron"; -import { normalize } from "path"; - -import Plugin from "./plugin"; -import { - getAllPlugins, - removePlugin, - persistPlugins, - installPlugins, - getPlugin, - getActivePlugins, - addPlugin, -} from "./store"; -import { - pluginsPath as storedPluginsPath, - setPluginsPath, - getPluginsFile, -} from "./globals"; -import router from "./router"; - -/** - * Sets up the required communication between the main and renderer processes. - * Additionally sets the plugins up using {@link usePlugins} if a pluginsPath is provided. - * @param {Object} options configuration for setting up the renderer facade. - * @param {confirmInstall} [options.confirmInstall] Function to validate that a plugin should be installed. - * @param {Boolean} [options.useFacade=true] Whether to make a facade to the plugins available in the renderer. - * @param {string} [options.pluginsPath] Optional path to the plugins folder. - * @returns {pluginManager|Object} A set of functions used to manage the plugin lifecycle if usePlugins is provided. - * @function - */ -export function init(options: any) { - if ( - !Object.prototype.hasOwnProperty.call(options, "useFacade") || - options.useFacade - ) { - // Enable IPC to be used by the facade - router(); - } - - // Create plugins protocol to serve plugins to renderer - registerPluginProtocol(); - - // perform full setup if pluginsPath is provided - if (options.pluginsPath) { - return usePlugins(options.pluginsPath); - } - - return {}; -} - -/** - * Create plugins protocol to provide plugins to renderer - * @private - * @returns {boolean} Whether the protocol registration was successful - */ -function registerPluginProtocol() { - return protocol.registerFileProtocol("plugin", (request, callback) => { - const entry = request.url.substr(8); - const url = normalize(storedPluginsPath + entry); - callback({ path: url }); - }); -} - -/** - * Set Pluggable Electron up to run from the pluginPath folder if it is provided and - * load plugins persisted in that folder. - * @param {string} pluginsPath Path to the plugins folder. Required if not yet set up. - * @returns {pluginManager} A set of functions used to manage the plugin lifecycle. - */ -export function usePlugins(pluginsPath: string) { - if (!pluginsPath) - throw Error( - "A path to the plugins folder is required to use Pluggable Electron" - ); - // Store the path to the plugins folder - setPluginsPath(pluginsPath); - - // Remove any registered plugins - for (const plugin of getAllPlugins()) { - if (plugin.name) removePlugin(plugin.name, false); - } - - // Read plugin list from plugins folder - const plugins = JSON.parse(readFileSync(getPluginsFile(), "utf-8")); - try { - // Create and store a Plugin instance for each plugin in list - for (const p in plugins) { - loadPlugin(plugins[p]); - } - persistPlugins(); - } catch (error) { - // Throw meaningful error if plugin loading fails - throw new Error( - "Could not successfully rebuild list of installed plugins.\n" + - error + - "\nPlease check the plugins.json file in the plugins folder." - ); - } - - // Return the plugin lifecycle functions - return getStore(); -} - -/** - * Check the given plugin object. If it is marked for uninstalling, the plugin files are removed. - * Otherwise a Plugin instance for the provided object is created and added to the store. - * @private - * @param {Object} plg Plugin info - */ -function loadPlugin(plg: any) { - // Create new plugin, populate it with plg details and save it to the store - const plugin = new Plugin(); - - for (const key in plg) { - if (Object.prototype.hasOwnProperty.call(plg, key)) { - // Use Object.defineProperty to set the properties as writable - Object.defineProperty(plugin, key, { - value: plg[key], - writable: true, - enumerable: true, - configurable: true, - }); - } - } - - addPlugin(plugin, false); - plugin.subscribe("pe-persist", persistPlugins); -} - -/** - * Returns the publicly available store functions. - * @returns {pluginManager} A set of functions used to manage the plugin lifecycle. - */ -export function getStore() { - if (!storedPluginsPath) { - throw new Error( - "The plugin path has not yet been set up. Please run usePlugins before accessing the store" - ); - } - - return { - installPlugins, - getPlugin, - getAllPlugins, - getActivePlugins, - removePlugin, - }; -} diff --git a/server/core/plugin/plugin.ts b/server/core/plugin/plugin.ts deleted file mode 100644 index f0fc073d7..000000000 --- a/server/core/plugin/plugin.ts +++ /dev/null @@ -1,213 +0,0 @@ -import { rmdir } from "fs/promises"; -import { resolve, join } from "path"; -import { manifest, extract } from "pacote"; -import * as Arborist from "@npmcli/arborist"; - -import { pluginsPath } from "./globals"; - -/** - * An NPM package that can be used as a Pluggable Electron plugin. - * Used to hold all the information and functions necessary to handle the plugin lifecycle. - */ -class Plugin { - /** - * @property {string} origin Original specification provided to fetch the package. - * @property {Object} installOptions Options provided to pacote when fetching the manifest. - * @property {name} name The name of the plugin as defined in the manifest. - * @property {string} url Electron URL where the package can be accessed. - * @property {string} version Version of the package as defined in the manifest. - * @property {Array} activationPoints List of {@link ./Execution-API#activationPoints|activation points}. - * @property {string} main The entry point as defined in the main entry of the manifest. - * @property {string} description The description of plugin as defined in the manifest. - * @property {string} icon The icon of plugin as defined in the manifest. - */ - origin?: string; - installOptions: any; - name?: string; - url?: string; - version?: string; - activationPoints?: Array; - main?: string; - description?: string; - icon?: string; - - /** @private */ - _active = false; - - /** - * @private - * @property {Object.} #listeners A list of callbacks to be executed when the Plugin is updated. - */ - listeners: Record void> = {}; - - /** - * Set installOptions with defaults for options that have not been provided. - * @param {string} [origin] Original specification provided to fetch the package. - * @param {Object} [options] Options provided to pacote when fetching the manifest. - */ - constructor(origin?: string, options = {}) { - const defaultOpts = { - version: false, - fullMetadata: false, - Arborist, - }; - - this.origin = origin; - this.installOptions = { ...defaultOpts, ...options }; - } - - /** - * Package name with version number. - * @type {string} - */ - get specifier() { - return ( - this.origin + - (this.installOptions.version ? "@" + this.installOptions.version : "") - ); - } - - /** - * Whether the plugin should be registered with its activation points. - * @type {boolean} - */ - get active() { - return this._active; - } - - /** - * Set Package details based on it's manifest - * @returns {Promise.} Resolves to true when the action completed - */ - async getManifest() { - // Get the package's manifest (package.json object) - try { - const mnf = await manifest(this.specifier, this.installOptions); - - // set the Package properties based on the it's manifest - this.name = mnf.name; - this.version = mnf.version; - this.activationPoints = mnf.activationPoints - ? (mnf.activationPoints as string[]) - : undefined; - this.main = mnf.main; - this.description = mnf.description; - this.icon = mnf.icon as any; - } catch (error) { - throw new Error( - `Package ${this.origin} does not contain a valid manifest: ${error}` - ); - } - - return true; - } - - /** - * Extract plugin to plugins folder. - * @returns {Promise.} This plugin - * @private - */ - async _install() { - try { - // import the manifest details - await this.getManifest(); - - // Install the package in a child folder of the given folder - await extract( - this.specifier, - join(pluginsPath ?? "", this.name ?? ""), - this.installOptions - ); - - if (!Array.isArray(this.activationPoints)) - throw new Error("The plugin does not contain any activation points"); - - // Set the url using the custom plugins protocol - this.url = `plugin://${this.name}/${this.main}`; - - this.emitUpdate(); - } catch (err) { - // Ensure the plugin is not stored and the folder is removed if the installation fails - this.setActive(false); - throw err; - } - - return [this]; - } - - /** - * Subscribe to updates of this plugin - * @param {string} name name of the callback to register - * @param {callback} cb The function to execute on update - */ - subscribe(name: string, cb: () => void) { - this.listeners[name] = cb; - } - - /** - * Remove subscription - * @param {string} name name of the callback to remove - */ - unsubscribe(name: string) { - delete this.listeners[name]; - } - - /** - * Execute listeners - */ - emitUpdate() { - for (const cb in this.listeners) { - this.listeners[cb].call(null, this); - } - } - - /** - * Check for updates and install if available. - * @param {string} version The version to update to. - * @returns {boolean} Whether an update was performed. - */ - async update(version = false) { - if (await this.isUpdateAvailable()) { - this.installOptions.version = version; - await this._install(); - return true; - } - - return false; - } - - /** - * Check if a new version of the plugin is available at the origin. - * @returns the latest available version if a new version is available or false if not. - */ - async isUpdateAvailable() { - if (this.origin) { - const mnf = await manifest(this.origin); - return mnf.version !== this.version ? mnf.version : false; - } - } - - /** - * Remove plugin and refresh renderers. - * @returns {Promise} - */ - async uninstall() { - const plgPath = resolve(pluginsPath ?? "", this.name ?? ""); - await rmdir(plgPath, { recursive: true }); - - this.emitUpdate(); - } - - /** - * Set a plugin's active state. This determines if a plugin should be loaded on initialisation. - * @param {boolean} active State to set _active to - * @returns {Plugin} This plugin - */ - setActive(active: boolean) { - this._active = active; - this.emitUpdate(); - return this; - } -} - -export default Plugin; diff --git a/server/core/plugin/router.ts b/server/core/plugin/router.ts deleted file mode 100644 index 09c79485b..000000000 --- a/server/core/plugin/router.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { ipcMain, webContents } from "electron"; - -import { - getPlugin, - getActivePlugins, - installPlugins, - removePlugin, - getAllPlugins, -} from "./store"; -import { pluginsPath } from "./globals"; -import Plugin from "./plugin"; - -// Throw an error if pluginsPath has not yet been provided by usePlugins. -const checkPluginsPath = () => { - if (!pluginsPath) - throw Error("Path to plugins folder has not yet been set up."); -}; -let active = false; -/** - * Provide the renderer process access to the plugins. - **/ -export default function () { - if (active) return; - // Register IPC route to install a plugin - ipcMain.handle("pluggable:install", async (e, plugins) => { - checkPluginsPath(); - - // Install and activate all provided plugins - const installed = await installPlugins(plugins); - return JSON.parse(JSON.stringify(installed)); - }); - - // Register IPC route to uninstall a plugin - ipcMain.handle("pluggable:uninstall", async (e, plugins, reload) => { - checkPluginsPath(); - - // Uninstall all provided plugins - for (const plg of plugins) { - const plugin = getPlugin(plg); - await plugin.uninstall(); - if (plugin.name) removePlugin(plugin.name); - } - - // Reload all renderer pages if needed - reload && webContents.getAllWebContents().forEach((wc) => wc.reload()); - return true; - }); - - // Register IPC route to update a plugin - ipcMain.handle("pluggable:update", async (e, plugins, reload) => { - checkPluginsPath(); - - // Update all provided plugins - const updated: Plugin[] = []; - for (const plg of plugins) { - const plugin = getPlugin(plg); - const res = await plugin.update(); - if (res) updated.push(plugin); - } - - // Reload all renderer pages if needed - if (updated.length && reload) - webContents.getAllWebContents().forEach((wc) => wc.reload()); - - return JSON.parse(JSON.stringify(updated)); - }); - - // Register IPC route to check if updates are available for a plugin - ipcMain.handle("pluggable:updatesAvailable", (e, names) => { - checkPluginsPath(); - - const plugins = names - ? names.map((name: string) => getPlugin(name)) - : getAllPlugins(); - - const updates: Record = {}; - for (const plugin of plugins) { - updates[plugin.name] = plugin.isUpdateAvailable(); - } - return updates; - }); - - // Register IPC route to get the list of active plugins - ipcMain.handle("pluggable:getActivePlugins", () => { - checkPluginsPath(); - return JSON.parse(JSON.stringify(getActivePlugins())); - }); - - // Register IPC route to toggle the active state of a plugin - ipcMain.handle("pluggable:togglePluginActive", (e, plg, active) => { - checkPluginsPath(); - const plugin = getPlugin(plg); - return JSON.parse(JSON.stringify(plugin.setActive(active))); - }); - - active = true; -} diff --git a/server/core/plugin/store.ts b/server/core/plugin/store.ts deleted file mode 100644 index cfd25e5ca..000000000 --- a/server/core/plugin/store.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Provides access to the plugins stored by Pluggable Electron - * @typedef {Object} pluginManager - * @prop {getPlugin} getPlugin - * @prop {getAllPlugins} getAllPlugins - * @prop {getActivePlugins} getActivePlugins - * @prop {installPlugins} installPlugins - * @prop {removePlugin} removePlugin - */ - -import { writeFileSync } from "fs"; -import Plugin from "./plugin"; -import { getPluginsFile } from "./globals"; - -/** - * @module store - * @private - */ - -/** - * Register of installed plugins - * @type {Object.} plugin - List of installed plugins - */ -const plugins: Record = {}; - -/** - * Get a plugin from the stored plugins. - * @param {string} name Name of the plugin to retrieve - * @returns {Plugin} Retrieved plugin - * @alias pluginManager.getPlugin - */ -export function getPlugin(name: string) { - if (!Object.prototype.hasOwnProperty.call(plugins, name)) { - throw new Error(`Plugin ${name} does not exist`); - } - - return plugins[name]; -} - -/** - * Get list of all plugin objects. - * @returns {Array.} All plugin objects - * @alias pluginManager.getAllPlugins - */ -export function getAllPlugins() { - return Object.values(plugins); -} - -/** - * Get list of active plugin objects. - * @returns {Array.} Active plugin objects - * @alias pluginManager.getActivePlugins - */ -export function getActivePlugins() { - return Object.values(plugins).filter((plugin) => plugin.active); -} - -/** - * Remove plugin from store and maybe save stored plugins to file - * @param {string} name Name of the plugin to remove - * @param {boolean} persist Whether to save the changes to plugins to file - * @returns {boolean} Whether the delete was successful - * @alias pluginManager.removePlugin - */ -export function removePlugin(name: string, persist = true) { - const del = delete plugins[name]; - if (persist) persistPlugins(); - return del; -} - -/** - * Add plugin to store and maybe save stored plugins to file - * @param {Plugin} plugin Plugin to add to store - * @param {boolean} persist Whether to save the changes to plugins to file - * @returns {void} - */ -export function addPlugin(plugin: Plugin, persist = true) { - if (plugin.name) plugins[plugin.name] = plugin; - if (persist) { - persistPlugins(); - plugin.subscribe("pe-persist", persistPlugins); - } -} - -/** - * Save stored plugins to file - * @returns {void} - */ -export function persistPlugins() { - const persistData: Record = {}; - for (const name in plugins) { - persistData[name] = plugins[name]; - } - writeFileSync(getPluginsFile(), JSON.stringify(persistData), "utf8"); -} - -/** - * Create and install a new plugin for the given specifier. - * @param {Array.} plugins A list of NPM specifiers, or installation configuration objects. - * @param {boolean} [store=true] Whether to store the installed plugins in the store - * @returns {Promise.>} New plugin - * @alias pluginManager.installPlugins - */ -export async function installPlugins(plugins: any, store = true) { - const installed: Plugin[] = []; - for (const plg of plugins) { - // Set install options and activation based on input type - const isObject = typeof plg === "object"; - const spec = isObject ? [plg.specifier, plg] : [plg]; - const activate = isObject ? plg.activate !== false : true; - - // Install and possibly activate plugin - const plugin = new Plugin(...spec); - await plugin._install(); - if (activate) plugin.setActive(true); - - // Add plugin to store if needed - if (store) addPlugin(plugin); - installed.push(plugin); - } - - // Return list of all installed plugins - return installed; -} - -/** - * @typedef {Object.} installOptions The {@link https://www.npmjs.com/package/pacote|pacote} - * options used to install the plugin with some extra options. - * @param {string} specifier the NPM specifier that identifies the package. - * @param {boolean} [activate] Whether this plugin should be activated after installation. Defaults to true. - */ diff --git a/server/core/pre-install/.gitkeep b/server/data/.gitkeep similarity index 100% rename from server/core/pre-install/.gitkeep rename to server/data/.gitkeep diff --git a/server/handlers/download.ts b/server/handlers/download.ts deleted file mode 100644 index 3a1fc36d1..000000000 --- a/server/handlers/download.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { app, ipcMain } from 'electron' -import { DownloadManager } from '../managers/download' -import { resolve, join } from 'path' -import { WindowManager } from '../managers/window' -import request from 'request' -import { createWriteStream, unlink } from 'fs' -const progress = require('request-progress') - -export function handleDownloaderIPCs() { - /** - * Handles the "pauseDownload" IPC message by pausing the download associated with the provided fileName. - * @param _event - The IPC event object. - * @param fileName - The name of the file being downloaded. - */ - ipcMain.handle('pauseDownload', async (_event, fileName) => { - DownloadManager.instance.networkRequests[fileName]?.pause() - }) - - /** - * Handles the "resumeDownload" IPC message by resuming the download associated with the provided fileName. - * @param _event - The IPC event object. - * @param fileName - The name of the file being downloaded. - */ - ipcMain.handle('resumeDownload', async (_event, fileName) => { - DownloadManager.instance.networkRequests[fileName]?.resume() - }) - - /** - * Handles the "abortDownload" IPC message by aborting the download associated with the provided fileName. - * The network request associated with the fileName is then removed from the networkRequests object. - * @param _event - The IPC event object. - * @param fileName - The name of the file being downloaded. - */ - ipcMain.handle('abortDownload', async (_event, fileName) => { - const rq = DownloadManager.instance.networkRequests[fileName] - DownloadManager.instance.networkRequests[fileName] = undefined - const userDataPath = app.getPath('userData') - const fullPath = join(userDataPath, fileName) - rq?.abort() - let result = 'NULL' - unlink(fullPath, function (err) { - if (err && err.code == 'ENOENT') { - result = `File not exist: ${err}` - } else if (err) { - result = `File delete error: ${err}` - } else { - result = 'File deleted successfully' - } - console.debug( - `Delete file ${fileName} from ${fullPath} result: ${result}` - ) - }) - }) - - /** - * Downloads a file from a given URL. - * @param _event - The IPC event object. - * @param url - The URL to download the file from. - * @param fileName - The name to give the downloaded file. - */ - ipcMain.handle('downloadFile', async (_event, url, fileName) => { - const userDataPath = join(app.getPath('home'), 'jan') - const destination = resolve(userDataPath, fileName) - const rq = request(url) - - progress(rq, {}) - .on('progress', function (state: any) { - WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_UPDATE', - { - ...state, - fileName, - } - ) - }) - .on('error', function (err: Error) { - WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_ERROR', - { - fileName, - err, - } - ) - }) - .on('end', function () { - if (DownloadManager.instance.networkRequests[fileName]) { - WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_COMPLETE', - { - fileName, - } - ) - DownloadManager.instance.setRequest(fileName, undefined) - } else { - WindowManager?.instance.currentWindow?.webContents.send( - 'FILE_DOWNLOAD_ERROR', - { - fileName, - err: 'Download cancelled', - } - ) - } - }) - .pipe(createWriteStream(destination)) - - DownloadManager.instance.setRequest(fileName, rq) - }) -} diff --git a/server/handlers/fs.ts b/server/handlers/fs.ts deleted file mode 100644 index c1e8a85e4..000000000 --- a/server/handlers/fs.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { app, ipcMain } from 'electron' -import * as fs from 'fs' -import { join } from 'path' - -/** - * Handles file system operations. - */ -export function handleFsIPCs() { - const userSpacePath = join(app.getPath('home'), 'jan') - - /** - * Gets the path to the user data directory. - * @param event - The event object. - * @returns A promise that resolves with the path to the user data directory. - */ - ipcMain.handle( - 'getUserSpace', - (): Promise => Promise.resolve(userSpacePath) - ) - - /** - * Checks whether the path is a directory. - * @param event - The event object. - * @param path - The path to check. - * @returns A promise that resolves with a boolean indicating whether the path is a directory. - */ - ipcMain.handle('isDirectory', (_event, path: string): Promise => { - const fullPath = join(userSpacePath, path) - return Promise.resolve( - fs.existsSync(fullPath) && fs.lstatSync(fullPath).isDirectory() - ) - }) - - /** - * Reads a file from the user data directory. - * @param event - The event object. - * @param path - The path of the file to read. - * @returns A promise that resolves with the contents of the file. - */ - ipcMain.handle('readFile', async (event, path: string): Promise => { - return new Promise((resolve, reject) => { - fs.readFile(join(userSpacePath, path), 'utf8', (err, data) => { - if (err) { - reject(err) - } else { - resolve(data) - } - }) - }) - }) - - /** - * Writes data to a file in the user data directory. - * @param event - The event object. - * @param path - The path of the file to write to. - * @param data - The data to write to the file. - * @returns A promise that resolves when the file has been written. - */ - ipcMain.handle( - 'writeFile', - async (event, path: string, data: string): Promise => { - return new Promise((resolve, reject) => { - fs.writeFile(join(userSpacePath, path), data, 'utf8', (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } - ) - - /** - * Creates a directory in the user data directory. - * @param event - The event object. - * @param path - The path of the directory to create. - * @returns A promise that resolves when the directory has been created. - */ - ipcMain.handle('mkdir', async (event, path: string): Promise => { - return new Promise((resolve, reject) => { - fs.mkdir(join(userSpacePath, path), { recursive: true }, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - }) - - /** - * Removes a directory in the user data directory. - * @param event - The event object. - * @param path - The path of the directory to remove. - * @returns A promise that resolves when the directory is removed successfully. - */ - ipcMain.handle('rmdir', async (event, path: string): Promise => { - return new Promise((resolve, reject) => { - fs.rmdir(join(userSpacePath, path), { recursive: true }, (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - }) - - /** - * Lists the files in a directory in the user data directory. - * @param event - The event object. - * @param path - The path of the directory to list files from. - * @returns A promise that resolves with an array of file names. - */ - ipcMain.handle( - 'listFiles', - async (event, path: string): Promise => { - return new Promise((resolve, reject) => { - fs.readdir(join(userSpacePath, path), (err, files) => { - if (err) { - reject(err) - } else { - resolve(files) - } - }) - }) - } - ) - - /** - * Deletes a file from the user data folder. - * @param _event - The IPC event object. - * @param filePath - The path to the file to delete. - * @returns A string indicating the result of the operation. - */ - ipcMain.handle('deleteFile', async (_event, filePath) => { - const fullPath = join(userSpacePath, filePath) - - let result = 'NULL' - fs.unlink(fullPath, function (err) { - if (err && err.code == 'ENOENT') { - result = `File not exist: ${err}` - } else if (err) { - result = `File delete error: ${err}` - } else { - result = 'File deleted successfully' - } - console.debug( - `Delete file ${filePath} from ${fullPath} result: ${result}` - ) - }) - - return result - }) -} diff --git a/server/handlers/plugin.ts b/server/handlers/plugin.ts deleted file mode 100644 index 22bf253e6..000000000 --- a/server/handlers/plugin.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { app, ipcMain } from "electron"; -import { readdirSync, rmdir, writeFileSync } from "fs"; -import { ModuleManager } from "../managers/module"; -import { join, extname } from "path"; -import { PluginManager } from "../managers/plugin"; -import { WindowManager } from "../managers/window"; -import { manifest, tarball } from "pacote"; - -export function handlePluginIPCs() { - /** - * Invokes a function from a plugin module in main node process. - * @param _event - The IPC event object. - * @param modulePath - The path to the plugin module. - * @param method - The name of the function to invoke. - * @param args - The arguments to pass to the function. - * @returns The result of the invoked function. - */ - ipcMain.handle( - "invokePluginFunc", - async (_event, modulePath, method, ...args) => { - const module = require( - /* webpackIgnore: true */ join( - app.getPath("userData"), - "plugins", - modulePath - ) - ); - ModuleManager.instance.setModule(modulePath, module); - - if (typeof module[method] === "function") { - return module[method](...args); - } else { - console.debug(module[method]); - console.error(`Function "${method}" does not exist in the module.`); - } - } - ); - - /** - * Returns the paths of the base plugins. - * @param _event - The IPC event object. - * @returns An array of paths to the base plugins. - */ - ipcMain.handle("basePlugins", async (_event) => { - const basePluginPath = join( - __dirname, - "../", - app.isPackaged - ? "../../app.asar.unpacked/core/pre-install" - : "../core/pre-install" - ); - return readdirSync(basePluginPath) - .filter((file) => extname(file) === ".tgz") - .map((file) => join(basePluginPath, file)); - }); - - /** - * Returns the path to the user's plugin directory. - * @param _event - The IPC event object. - * @returns The path to the user's plugin directory. - */ - ipcMain.handle("pluginPath", async (_event) => { - return join(app.getPath("userData"), "plugins"); - }); - - /** - * Deletes the `plugins` directory in the user data path and disposes of required modules. - * If the app is packaged, the function relaunches the app and exits. - * Otherwise, the function deletes the cached modules and sets up the plugins and reloads the main window. - * @param _event - The IPC event object. - * @param url - The URL to reload. - */ - ipcMain.handle("reloadPlugins", async (_event, url) => { - const userDataPath = app.getPath("userData"); - const fullPath = join(userDataPath, "plugins"); - - rmdir(fullPath, { recursive: true }, function (err) { - if (err) console.error(err); - ModuleManager.instance.clearImportedModules(); - - // just relaunch if packaged, should launch manually in development mode - if (app.isPackaged) { - app.relaunch(); - app.exit(); - } else { - for (const modulePath in ModuleManager.instance.requiredModules) { - delete require.cache[ - require.resolve( - join(app.getPath("userData"), "plugins", modulePath) - ) - ]; - } - PluginManager.instance.setupPlugins(); - WindowManager.instance.currentWindow?.reload(); - } - }); - }); - - /** - * Installs a remote plugin by downloading its tarball and writing it to a tgz file. - * @param _event - The IPC event object. - * @param pluginName - The name of the remote plugin to install. - * @returns A Promise that resolves to the path of the installed plugin file. - */ - ipcMain.handle("installRemotePlugin", async (_event, pluginName) => { - const destination = join( - app.getPath("userData"), - pluginName.replace(/^@.*\//, "") + ".tgz" - ); - return manifest(pluginName) - .then(async (manifest: any) => { - await tarball(manifest._resolved).then((data: Buffer) => { - writeFileSync(destination, data); - }); - }) - .then(() => destination); - }); -} diff --git a/server/lib/.gitkeep b/server/lib/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/main.ts b/server/main.ts index 5ba2045c1..582af5c61 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,31 +1,28 @@ -import { setupMenu } from './utils/menu' -import app from 'express' +import express from 'express' import bodyParser from 'body-parser' import fs from 'fs' -/** - * Managers - **/ -import { ModuleManager } from './managers/module' -import { PluginManager } from './managers/plugin' +import v1API from './v1' +const JAN_API_PORT = 1337; -const server = app() -server.use(bodyParser) +const server = express() +server.use(bodyParser.urlencoded()) +server.use(bodyParser.json()) const USER_ROOT_DIR = '.data' -server.post("fs", (req, res) => { - let op = req.body.op; - switch(op){ - case 'readFile': - fs.readFile(req.body.path, ()=>{}) - case 'writeFile': - fs.writeFile(req.body.path, Buffer.from(req.body.data, "base64"), ()=>{}) - } -}) - -server.listen(1337, ()=>{ - PluginManager.instance.migratePlugins() - PluginManager.instance.setupPlugins() - setupMenu() +server.use("/v1", v1API) + +// server.post("fs", (req, res) => { +// let op = req.body.op; +// switch(op){ +// case 'readFile': +// fs.readFile(req.body.path, ()=>{}) +// case 'writeFile': +// fs.writeFile(req.body.path, Buffer.from(req.body.data, "base64"), ()=>{}) +// } +// }) + +server.listen(JAN_API_PORT, () => { + console.log(`JAN API listening at: http://localhost:${JAN_API_PORT}`); }) diff --git a/server/managers/download.ts b/server/managers/download.ts deleted file mode 100644 index 08c089b74..000000000 --- a/server/managers/download.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Request } from "request"; - -/** - * Manages file downloads and network requests. - */ -export class DownloadManager { - public networkRequests: Record = {}; - - public static instance: DownloadManager = new DownloadManager(); - - constructor() { - if (DownloadManager.instance) { - return DownloadManager.instance; - } - } - /** - * Sets a network request for a specific file. - * @param {string} fileName - The name of the file. - * @param {Request | undefined} request - The network request to set, or undefined to clear the request. - */ - setRequest(fileName: string, request: Request | undefined) { - this.networkRequests[fileName] = request; - } -} diff --git a/server/managers/module.ts b/server/managers/module.ts deleted file mode 100644 index 43dda0fb6..000000000 --- a/server/managers/module.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { dispose } from "../utils/disposable"; - -/** - * Manages imported modules. - */ -export class ModuleManager { - public requiredModules: Record = {}; - - public static instance: ModuleManager = new ModuleManager(); - - constructor() { - if (ModuleManager.instance) { - return ModuleManager.instance; - } - } - - /** - * Sets a module. - * @param {string} moduleName - The name of the module. - * @param {any | undefined} nodule - The module to set, or undefined to clear the module. - */ - setModule(moduleName: string, nodule: any | undefined) { - this.requiredModules[moduleName] = nodule; - } - - /** - * Clears all imported modules. - */ - clearImportedModules() { - dispose(this.requiredModules); - this.requiredModules = {}; - } -} diff --git a/server/managers/plugin.ts b/server/managers/plugin.ts deleted file mode 100644 index 227eab34e..000000000 --- a/server/managers/plugin.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { app } from "electron"; -import { init } from "../core/plugin/index"; -import { join } from "path"; -import { rmdir } from "fs"; -import Store from "electron-store"; - -/** - * Manages plugin installation and migration. - */ -export class PluginManager { - public static instance: PluginManager = new PluginManager(); - - constructor() { - if (PluginManager.instance) { - return PluginManager.instance; - } - } - - /** - * Sets up the plugins by initializing the `plugins` module with the `confirmInstall` and `pluginsPath` options. - * The `confirmInstall` function always returns `true` to allow plugin installation. - * The `pluginsPath` option specifies the path to install plugins to. - */ - setupPlugins() { - init({ - // Function to check from the main process that user wants to install a plugin - confirmInstall: async (_plugins: string[]) => { - return true; - }, - // Path to install plugin to - pluginsPath: join(app.getPath("userData"), "plugins"), - }); - } - - /** - * Migrates the plugins by deleting the `plugins` directory in the user data path. - * If the `migrated_version` key in the `Store` object does not match the current app version, - * the function deletes the `plugins` directory and sets the `migrated_version` key to the current app version. - * @returns A Promise that resolves when the migration is complete. - */ - migratePlugins() { - return new Promise((resolve) => { - const store = new Store(); - if (store.get("migrated_version") !== app.getVersion()) { - console.debug("start migration:", store.get("migrated_version")); - const userDataPath = app.getPath("userData"); - const fullPath = join(userDataPath, "plugins"); - - rmdir(fullPath, { recursive: true }, function (err) { - if (err) console.error(err); - store.set("migrated_version", app.getVersion()); - console.debug("migrate plugins done"); - resolve(undefined); - }); - } else { - resolve(undefined); - } - }); - } -} diff --git a/server/managers/window.ts b/server/managers/window.ts deleted file mode 100644 index c930dd5ec..000000000 --- a/server/managers/window.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { BrowserWindow } from "electron"; - -/** - * Manages the current window instance. - */ -export class WindowManager { - public static instance: WindowManager = new WindowManager(); - public currentWindow?: BrowserWindow; - - constructor() { - if (WindowManager.instance) { - return WindowManager.instance; - } - } - - /** - * Creates a new window instance. - * @param {Electron.BrowserWindowConstructorOptions} options - The options to create the window with. - * @returns The created window instance. - */ - createWindow(options?: Electron.BrowserWindowConstructorOptions | undefined) { - this.currentWindow = new BrowserWindow({ - width: 1200, - minWidth: 800, - height: 800, - show: false, - trafficLightPosition: { - x: 10, - y: 15, - }, - titleBarStyle: "hidden", - vibrancy: "sidebar", - ...options, - }); - return this.currentWindow; - } -} diff --git a/server/nodemon.json b/server/nodemon.json new file mode 100644 index 000000000..0ea41ca96 --- /dev/null +++ b/server/nodemon.json @@ -0,0 +1,5 @@ +{ + "watch": ["main.ts", "v1"], + "ext": "ts, json", + "exec": "tsc && node ./build/main.js" +} \ No newline at end of file diff --git a/server/package.json b/server/package.json index 9523fa000..08c19ee31 100644 --- a/server/package.json +++ b/server/package.json @@ -51,26 +51,15 @@ "scripts": { "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", "test:e2e": "playwright test --workers=1", - "dev": "tsc -p . && electron .", - "build": "run-script-os", - "build:test": "run-script-os", - "build:test:darwin": "tsc -p . && electron-builder -p never -m --dir", - "build:test:win32": "tsc -p . && electron-builder -p never -w --dir", - "build:test:linux": "tsc -p . && electron-builder -p never -l --dir", - "build:darwin": "tsc -p . && electron-builder -p never -m", - "build:win32": "tsc -p . && electron-builder -p never -w", - "build:linux": "tsc -p . && electron-builder -p never --linux deb", - "build:publish": "run-script-os", - "build:publish:darwin": "tsc -p . && electron-builder -p onTagOrDraft -m --x64 --arm64", - "build:publish:win32": "tsc -p . && electron-builder -p onTagOrDraft -w", - "build:publish:linux": "tsc -p . && electron-builder -p onTagOrDraft -l deb" + "dev": "nodemon .", + "build": "tsc", + "build:test": "", + "build:publish": "" }, "dependencies": { "@npmcli/arborist": "^7.1.0", "@types/request": "^2.48.12", "@uiball/loaders": "^1.3.0", - "electron-store": "^8.1.0", - "electron-updater": "^6.1.4", "express": "^4.18.2", "pacote": "^17.0.4", "request": "^2.88.2", @@ -78,7 +67,6 @@ "use-debounce": "^9.0.4" }, "devDependencies": { - "@electron/notarize": "^2.1.0", "@playwright/test": "^1.38.1", "@types/body-parser": "^1.19.5", "@types/express": "^4.17.21", @@ -86,10 +74,8 @@ "@types/pacote": "^11.1.7", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", - "electron": "26.2.1", - "electron-builder": "^24.6.4", - "electron-playwright-helpers": "^1.6.0", "eslint-plugin-react": "^7.33.2", + "nodemon": "^3.0.1", "run-script-os": "^1.1.6" }, "installConfig": { diff --git a/server/tsconfig.json b/server/tsconfig.json index 3cc218f93..3363fdba6 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -15,6 +15,8 @@ "paths": { "*": ["node_modules/*"] }, "typeRoots": ["node_modules/@types"] }, + // "sourceMap": true, + "include": ["./**/*.ts"], "exclude": ["core", "build", "dist", "tests", "node_modules"] } diff --git a/server/utils/disposable.ts b/server/utils/disposable.ts deleted file mode 100644 index 462f7e3e5..000000000 --- a/server/utils/disposable.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function dispose(requiredModules: Record) { - for (const key in requiredModules) { - const module = requiredModules[key]; - if (typeof module["dispose"] === "function") { - module["dispose"](); - } - } -} diff --git a/server/utils/menu.ts b/server/utils/menu.ts deleted file mode 100644 index 65e009aef..000000000 --- a/server/utils/menu.ts +++ /dev/null @@ -1,111 +0,0 @@ -// @ts-nocheck -const { app, Menu, dialog } = require("electron"); -const isMac = process.platform === "darwin"; -const { autoUpdater } = require("electron-updater"); -import { compareSemanticVersions } from "./versionDiff"; - -const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [ - ...(isMac - ? [ - { - label: app.name, - submenu: [ - { role: "about" }, - { - label: "Check for Updates...", - click: () => - autoUpdater.checkForUpdatesAndNotify().then((e) => { - if ( - !e || - compareSemanticVersions( - app.getVersion(), - e.updateInfo.version - ) >= 0 - ) - dialog.showMessageBox({ - message: `There are currently no updates available.`, - }); - }), - }, - { type: "separator" }, - { role: "services" }, - { type: "separator" }, - { role: "hide" }, - { role: "hideOthers" }, - { role: "unhide" }, - { type: "separator" }, - { role: "quit" }, - ], - }, - ] - : []), - { - label: "Edit", - submenu: [ - { role: "undo" }, - { role: "redo" }, - { type: "separator" }, - { role: "cut" }, - { role: "copy" }, - { role: "paste" }, - ...(isMac - ? [ - { role: "pasteAndMatchStyle" }, - { role: "delete" }, - { role: "selectAll" }, - { type: "separator" }, - { - label: "Speech", - submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }], - }, - ] - : [{ role: "delete" }, { type: "separator" }, { role: "selectAll" }]), - ], - }, - { - label: "View", - submenu: [ - { role: "reload" }, - { role: "forceReload" }, - { role: "toggleDevTools" }, - { type: "separator" }, - { role: "resetZoom" }, - { role: "zoomIn" }, - { role: "zoomOut" }, - { type: "separator" }, - { role: "togglefullscreen" }, - ], - }, - { - label: "Window", - submenu: [ - { role: "minimize" }, - { role: "zoom" }, - ...(isMac - ? [ - { type: "separator" }, - { role: "front" }, - { type: "separator" }, - { role: "window" }, - ] - : [{ role: "close" }]), - ], - }, - { - role: "help", - submenu: [ - { - label: "Learn More", - click: async () => { - const { shell } = require("electron"); - await shell.openExternal("https://jan.ai/"); - }, - }, - ], - }, -]; - -export const setupMenu = () => { - const menu = Menu.buildFromTemplate(template); - Menu.setApplicationMenu(menu); -}; diff --git a/server/utils/versionDiff.ts b/server/utils/versionDiff.ts deleted file mode 100644 index 25934e87f..000000000 --- a/server/utils/versionDiff.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const compareSemanticVersions = (a: string, b: string) => { - - // 1. Split the strings into their parts. - const a1 = a.split('.'); - const b1 = b.split('.'); - // 2. Contingency in case there's a 4th or 5th version - const len = Math.min(a1.length, b1.length); - // 3. Look through each version number and compare. - for (let i = 0; i < len; i++) { - const a2 = +a1[ i ] || 0; - const b2 = +b1[ i ] || 0; - - if (a2 !== b2) { - return a2 > b2 ? 1 : -1; - } - } - - // 4. We hit this if the all checked versions so far are equal - // - return b1.length - a1.length; -}; \ No newline at end of file diff --git a/server/v1/assistants/index.ts b/server/v1/assistants/index.ts new file mode 100644 index 000000000..7b51b801f --- /dev/null +++ b/server/v1/assistants/index.ts @@ -0,0 +1,5 @@ +import { Request, Response } from 'express' + +export default function route(req: Request, res: Response){ + +} \ No newline at end of file diff --git a/server/v1/chat/index.ts b/server/v1/chat/index.ts new file mode 100644 index 000000000..7b51b801f --- /dev/null +++ b/server/v1/chat/index.ts @@ -0,0 +1,5 @@ +import { Request, Response } from 'express' + +export default function route(req: Request, res: Response){ + +} \ No newline at end of file diff --git a/server/v1/index.ts b/server/v1/index.ts new file mode 100644 index 000000000..7528b917d --- /dev/null +++ b/server/v1/index.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express' + +import assistantsAPI from './assistants' +import chatCompletionAPI from './chat' +import modelsAPI from './models' +import threadsAPI from './threads' + +export default function route(req: Request, res: Response){ + console.log(req.path.split("/")[1]) + switch (req.path.split("/")[1]){ + case 'assistants': + assistantsAPI(req, res) + case 'chat': + chatCompletionAPI(req, res) + case 'models': + modelsAPI(req, res) + case 'threads': + threadsAPI(req, res) + } +} \ No newline at end of file diff --git a/server/v1/models/downloadModel.ts b/server/v1/models/downloadModel.ts new file mode 100644 index 000000000..89db0cfce --- /dev/null +++ b/server/v1/models/downloadModel.ts @@ -0,0 +1,5 @@ +import { Request, Response } from 'express' + +export default function controller(req: Request, res: Response){ + +} \ No newline at end of file diff --git a/server/v1/models/index.ts b/server/v1/models/index.ts new file mode 100644 index 000000000..091e46283 --- /dev/null +++ b/server/v1/models/index.ts @@ -0,0 +1,18 @@ +import { Request, Response } from 'express' + +import downloadModelController from './downloadModel' + +function getModelController(req: Request, res: Response){ + +} + +export default function route(req: Request, res: Response){ + switch(req.method){ + case 'get': + getModelController(req, res) + break; + case 'post': + downloadModelController(req, res) + break; + } +} \ No newline at end of file diff --git a/server/v1/threads/index.ts b/server/v1/threads/index.ts new file mode 100644 index 000000000..7b51b801f --- /dev/null +++ b/server/v1/threads/index.ts @@ -0,0 +1,5 @@ +import { Request, Response } from 'express' + +export default function route(req: Request, res: Response){ + +} \ No newline at end of file From ece03faee3106e189025f3f1047529486bf77374 Mon Sep 17 00:00:00 2001 From: 0xSage Date: Fri, 1 Dec 2023 18:02:19 +0800 Subject: [PATCH 008/179] docs: add assistants nits --- docs/docs/specs/engineering/assistants.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/specs/engineering/assistants.md b/docs/docs/specs/engineering/assistants.md index ea0ec0955..d03989d66 100644 --- a/docs/docs/specs/engineering/assistants.md +++ b/docs/docs/specs/engineering/assistants.md @@ -38,6 +38,10 @@ In Jan, assistants are `primary` entities with the following capabilities: ## `assistant.json` +- Each `assistant` folder contains an `assistant.json` file, which is a representation of an assistant. +- `assistant.json` contains metadata and model parameter overrides +- There are no required fields. + ```js { "id": "asst_abc123", // Defaults to foldername From f5c19bccc5b05f26e34a2e753d2943e34a29d3fa Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Fri, 1 Dec 2023 23:02:35 +0700 Subject: [PATCH 009/179] Switch to fastify + add model CRUD impl --- server/data/models/.gitkeep | 0 server/data/threads/.gitkeep | 0 server/main.ts | 29 ++++--------- server/package.json | 1 + server/v1/assistants/index.ts | 11 +++-- server/v1/chat/index.ts | 14 ++++-- server/v1/index.ts | 47 +++++++++++++------- server/v1/models/downloadModel.ts | 24 +++++++++-- server/v1/models/index.ts | 71 +++++++++++++++++++++++++------ server/v1/models/modelOp.ts | 11 +++++ server/v1/threads/index.ts | 9 ++-- 11 files changed, 154 insertions(+), 63 deletions(-) create mode 100644 server/data/models/.gitkeep create mode 100644 server/data/threads/.gitkeep create mode 100644 server/v1/models/modelOp.ts diff --git a/server/data/models/.gitkeep b/server/data/models/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/data/threads/.gitkeep b/server/data/threads/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/server/main.ts b/server/main.ts index 582af5c61..5466a27d8 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,28 +1,17 @@ -import express from 'express' -import bodyParser from 'body-parser' -import fs from 'fs' +import fastify from 'fastify' + import v1API from './v1' - const JAN_API_PORT = 1337; - -const server = express() -server.use(bodyParser.urlencoded()) -server.use(bodyParser.json()) +const server = fastify() const USER_ROOT_DIR = '.data' -server.use("/v1", v1API) +server.register(v1API, {prefix: "/api/v1"}) -// server.post("fs", (req, res) => { -// let op = req.body.op; -// switch(op){ -// case 'readFile': -// fs.readFile(req.body.path, ()=>{}) -// case 'writeFile': -// fs.writeFile(req.body.path, Buffer.from(req.body.data, "base64"), ()=>{}) -// } -// }) -server.listen(JAN_API_PORT, () => { - console.log(`JAN API listening at: http://localhost:${JAN_API_PORT}`); +server.listen({ + port: JAN_API_PORT, + host: "0.0.0.0" +}).then(()=>{ + console.log(`JAN API listening at: http://0.0.0.0:${JAN_API_PORT}`); }) diff --git a/server/package.json b/server/package.json index 08c19ee31..405f2bf5b 100644 --- a/server/package.json +++ b/server/package.json @@ -75,6 +75,7 @@ "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "eslint-plugin-react": "^7.33.2", + "fastify": "^4.24.3", "nodemon": "^3.0.1", "run-script-os": "^1.1.6" }, diff --git a/server/v1/assistants/index.ts b/server/v1/assistants/index.ts index 7b51b801f..c722195d0 100644 --- a/server/v1/assistants/index.ts +++ b/server/v1/assistants/index.ts @@ -1,5 +1,8 @@ -import { Request, Response } from 'express' +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' -export default function route(req: Request, res: Response){ - -} \ No newline at end of file +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers for assistants here + // app.get("/", controller) + // app.post("/", controller) +} +export default router; \ No newline at end of file diff --git a/server/v1/chat/index.ts b/server/v1/chat/index.ts index 7b51b801f..cb5fbf120 100644 --- a/server/v1/chat/index.ts +++ b/server/v1/chat/index.ts @@ -1,5 +1,11 @@ -import { Request, Response } from 'express' +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' -export default function route(req: Request, res: Response){ - -} \ No newline at end of file +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers for here + // app.get("/", controller) + + app.post("/", (req, res) => { + req.body + }) +} +export default router; \ No newline at end of file diff --git a/server/v1/index.ts b/server/v1/index.ts index 7528b917d..8a153cbaf 100644 --- a/server/v1/index.ts +++ b/server/v1/index.ts @@ -1,20 +1,37 @@ -import { Request, Response } from 'express' - import assistantsAPI from './assistants' import chatCompletionAPI from './chat' import modelsAPI from './models' import threadsAPI from './threads' -export default function route(req: Request, res: Response){ - console.log(req.path.split("/")[1]) - switch (req.path.split("/")[1]){ - case 'assistants': - assistantsAPI(req, res) - case 'chat': - chatCompletionAPI(req, res) - case 'models': - modelsAPI(req, res) - case 'threads': - threadsAPI(req, res) - } -} \ No newline at end of file +import { FastifyInstance, FastifyPluginAsync } from 'fastify' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts) => { + app.register( + assistantsAPI, + { + prefix: "/assisstants" + } + ) + + app.register( + chatCompletionAPI, + { + prefix: "/chat/completion" + } + ) + + app.register( + modelsAPI, + { + prefix: "/models" + } + ) + + app.register( + threadsAPI, + { + prefix: "/threads" + } + ) +} +export default router; \ No newline at end of file diff --git a/server/v1/models/downloadModel.ts b/server/v1/models/downloadModel.ts index 89db0cfce..d564a2207 100644 --- a/server/v1/models/downloadModel.ts +++ b/server/v1/models/downloadModel.ts @@ -1,5 +1,23 @@ -import { Request, Response } from 'express' +import { RouteHandlerMethod, FastifyRequest, FastifyReply } from 'fastify' +import { MODEL_FOLDER_PATH } from "./index" +import fs from 'fs/promises' -export default function controller(req: Request, res: Response){ +const controller: RouteHandlerMethod = async (req: FastifyRequest, res: FastifyReply) => { + //TODO: download models impl + //Mirror logic from JanModelExtension.downloadModel? + let model = req.body.model; -} \ No newline at end of file + // Fetching logic + // const directoryPath = join(MODEL_FOLDER_PATH, model.id) + // await fs.mkdir(directoryPath) + + // const path = join(directoryPath, model.id) + // downloadFile(model.source_url, path) + // TODO: Different model downloader from different model vendor + + res.status(200).send({ + status: "Ok" + }) +} + +export default controller; \ No newline at end of file diff --git a/server/v1/models/index.ts b/server/v1/models/index.ts index 091e46283..22c551300 100644 --- a/server/v1/models/index.ts +++ b/server/v1/models/index.ts @@ -1,18 +1,61 @@ -import { Request, Response } from 'express' -import downloadModelController from './downloadModel' +export const MODEL_FOLDER_PATH = "./data/models" +export const _modelMetadataFileName = 'model.json' -function getModelController(req: Request, res: Response){ - -} +import fs from 'fs/promises' +import { Model } from '@janhq/core' +import { join } from 'path' -export default function route(req: Request, res: Response){ - switch(req.method){ - case 'get': - getModelController(req, res) - break; - case 'post': - downloadModelController(req, res) - break; +// map string => model object +let modelIndex = new Map(); +async function buildModelIndex(){ + let modelIds = await fs.readdir(MODEL_FOLDER_PATH); + // TODO: read modelFolders to get model info, mirror JanModelExtension? + try{ + for(let modelId in modelIds){ + let path = join(MODEL_FOLDER_PATH, modelId) + let fileData = await fs.readFile(join(path, _modelMetadataFileName)) + modelIndex.set(modelId, JSON.parse(fileData.toString("utf-8")) as Model) + } } -} \ No newline at end of file + catch(err){ + console.error("build model index failed. ", err); + } +} +buildModelIndex() + +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' +import downloadModelController from './downloadModel' +import { startModel, stopModel } from './modelOp' + +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers declaration here + + ///////////// CRUD //////////////// + // Model listing + app.get("/", async (req, res) => { + res.status(200).send( + modelIndex.values() + ) + }) + + // Retrieve model info + app.get("/:id", (req, res) => { + res.status(200).send( + modelIndex.get(req.params.id) + ) + }) + + // Delete model + app.delete("/:id", (req, res) => { + modelIndex.delete(req.params) + + // TODO: delete on disk + }) + + ///////////// Other ops //////////////// + app.post("/", downloadModelController) + app.put("/start", startModel) + app.put("/stop", stopModel) +} +export default router; \ No newline at end of file diff --git a/server/v1/models/modelOp.ts b/server/v1/models/modelOp.ts new file mode 100644 index 000000000..f2c7ffe75 --- /dev/null +++ b/server/v1/models/modelOp.ts @@ -0,0 +1,11 @@ +import {FastifyRequest, FastifyReply} from 'fastify' + +export async function startModel(req: FastifyRequest, res: FastifyReply): Promise { + + +} + +export async function stopModel(req: FastifyRequest, res: FastifyReply): Promise { + + +} \ No newline at end of file diff --git a/server/v1/threads/index.ts b/server/v1/threads/index.ts index 7b51b801f..e63f9e8d8 100644 --- a/server/v1/threads/index.ts +++ b/server/v1/threads/index.ts @@ -1,5 +1,8 @@ -import { Request, Response } from 'express' +import { FastifyInstance, FastifyPluginAsync, FastifyPluginOptions } from 'fastify' -export default function route(req: Request, res: Response){ +const router: FastifyPluginAsync = async (app: FastifyInstance, opts: FastifyPluginOptions) => { + //TODO: Add controllers declaration here -} \ No newline at end of file + // app.get() +} +export default router; \ No newline at end of file From 87484e84288d2cf959f50ace9f1c58bc36933d21 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:25:32 +0900 Subject: [PATCH 010/179] docs: update assistant object --- docs/openapi/specs/assistants.yaml | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/openapi/specs/assistants.yaml b/docs/openapi/specs/assistants.yaml index 9db27e308..4565d6ef0 100644 --- a/docs/openapi/specs/assistants.yaml +++ b/docs/openapi/specs/assistants.yaml @@ -3,10 +3,6 @@ components: AssistantObject: type: object properties: - avatar: - type: string - description: "URL of the assistant's avatar. Jan-specific property." - example: "https://lala.png" id: type: string description: "The identifier of the assistant." @@ -23,6 +19,7 @@ components: type: integer format: int64 description: "Unix timestamp representing the creation time of the assistant." + example: 1698984975 name: type: string description: "Name of the assistant." @@ -30,6 +27,11 @@ components: description: type: string description: "Description of the assistant. Can be null." + example: null + avatar: + type: string + description: "URL of the assistant's avatar. Jan-specific property." + example: "https://pic.png" models: type: array description: "List of models associated with the assistant. Jan-specific property." @@ -38,7 +40,11 @@ components: properties: model_id: type: string - # Additional properties for models can be added here + example: model_0 + instructions: + type: string + description: "A system prompt for the assistant" + example: Be concise events: type: object description: "Event subscription settings for the assistant." @@ -55,10 +61,6 @@ components: metadata: type: object description: "Metadata associated with the assistant." - required: - - name - - models - - events ListAssistantsResponse: type: object From 5dfa40d57d0e08d8faa0a9e796da6131931b0896 Mon Sep 17 00:00:00 2001 From: hieu-jan <150573299+hieu-jan@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:27:40 +0900 Subject: [PATCH 011/179] remove current response --- docs/openapi/specs/assistants.yaml | 69 +----------------------------- 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/docs/openapi/specs/assistants.yaml b/docs/openapi/specs/assistants.yaml index 4565d6ef0..c5fb12b6d 100644 --- a/docs/openapi/specs/assistants.yaml +++ b/docs/openapi/specs/assistants.yaml @@ -64,80 +64,15 @@ components: ListAssistantsResponse: type: object - properties: - id: - type: string - description: "The identifier of the model that was deleted." - example: "model-zephyr-7B" - object: - type: string - description: "Type of the object, indicating it's a model." - default: "model" - deleted: - type: boolean - description: "Indicates whether the model was successfully deleted." - example: true CreateAssistantResponse: type: object - properties: - id: - type: string - description: "The identifier of the model that was deleted." - example: "model-zephyr-7B" - object: - type: string - description: "Type of the object, indicating it's a model." - default: "model" - deleted: - type: boolean - description: "Indicates whether the model was successfully deleted." - example: true RetrieveAssistantResponse: type: object - properties: - id: - type: string - description: "The identifier of the model that was deleted." - example: "model-zephyr-7B" - object: - type: string - description: "Type of the object, indicating it's a model." - default: "model" - deleted: - type: boolean - description: "Indicates whether the model was successfully deleted." - example: true - + ModifyAssistantResponse: type: object - properties: - id: - type: string - description: "The identifier of the model that was deleted." - example: "model-zephyr-7B" - object: - type: string - description: "Type of the object, indicating it's a model." - default: "model" - deleted: - type: boolean - description: "Indicates whether the model was successfully deleted." - example: true DeleteAssistantResponse: - type: object - properties: - id: - type: string - description: "The identifier of the model that was deleted." - example: "model-zephyr-7B" - object: - type: string - description: "Type of the object, indicating it's a model." - default: "model" - deleted: - type: boolean - description: "Indicates whether the model was successfully deleted." - example: true \ No newline at end of file + type: object \ No newline at end of file From fc6f8d2c0a2677660d572e20e067830a905930fe Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 11:41:30 +0700 Subject: [PATCH 012/179] decapriate some 70b models --- models/storytelling-70b/model.json | 24 ------------------------ models/xwin-70b/model.json | 24 ------------------------ models/yarn-70b/model.json | 21 --------------------- 3 files changed, 69 deletions(-) delete mode 100644 models/storytelling-70b/model.json delete mode 100644 models/xwin-70b/model.json delete mode 100644 models/yarn-70b/model.json diff --git a/models/storytelling-70b/model.json b/models/storytelling-70b/model.json deleted file mode 100644 index 76e6f7922..000000000 --- a/models/storytelling-70b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/GOAT-70B-Storytelling-GGUF/resolve/main/goat-70b-storytelling.Q5_K_M.gguf", - "id": "storytelling-70b", - "object": "model", - "name": "Storytelling 70B", - "version": "1.0", - "description": "The GOAT-70B-Storytelling model is designed for autonomous story-writing, including crafting books and movie scripts. Based on the LLaMA 2 70B architecture, this model excels in generating cohesive and engaging narratives using inputs like plot outlines and character profiles.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "### USER: ", - "ai_prompt": "\n### ASSISTANT: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "GOAT-AI, The Bloke", - "tags": ["General Use", "Writing"], - "size": 48750000000 - } - } - \ No newline at end of file diff --git a/models/xwin-70b/model.json b/models/xwin-70b/model.json deleted file mode 100644 index a5c1647b0..000000000 --- a/models/xwin-70b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/Xwin-LM-70B-V0.1-GGUF/resolve/main/xwin-lm-70b-v0.1.Q5_K_M.gguf", - "id": "xwin-70b", - "object": "model", - "name": "Xwin LM 70B", - "version": "1.0", - "description": "Xwin-LM, based on Llama2 models, emphasizes alignment and exhibits advanced language understanding, text generation, and role-playing abilities.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "USER: ", - "ai_prompt": "ASSISTANT: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "Xwin-LM, The Bloke", - "tags": ["General Use", "Role-playing"], - "size": 48750000000 - } - } - \ No newline at end of file diff --git a/models/yarn-70b/model.json b/models/yarn-70b/model.json deleted file mode 100644 index 67d8d3804..000000000 --- a/models/yarn-70b/model.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/Yarn-Llama-2-70B-32k-GGUF/resolve/main/yarn-llama-2-70b-32k.Q5_K_M.gguf", - "id": "yarn-70b", - "object": "model", - "name": "Yarn 32k 70B", - "version": "1,0", - "description": "Yarn-Llama-2-70b-32k is designed specifically for handling long contexts. It represents an extension of the Llama-2-70b-hf model, now supporting a 32k token context window.", - "format": "gguf", - "settings": { - "ctx_len": 4096 - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "NousResearch, The Bloke", - "tags": ["General Use", "Big Context Length"], - "size": 48750000000 - } - } - \ No newline at end of file From 2e96aacf55b4061dfb5ca5560e9691484a659728 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 11:42:46 +0700 Subject: [PATCH 013/179] decapriate some 34b models --- models/dolphin-yi-34b/model.json | 24 ------------------------ models/lzlv-70b/model.json | 2 +- 2 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 models/dolphin-yi-34b/model.json diff --git a/models/dolphin-yi-34b/model.json b/models/dolphin-yi-34b/model.json deleted file mode 100644 index 3b1bf3619..000000000 --- a/models/dolphin-yi-34b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/dolphin-2_2-yi-34b-GGUF/resolve/main/dolphin-2_2-yi-34b.Q5_K_M.gguf", - "id": "dolphin-yi-34b", - "object": "model", - "name": "Dolphin Yi 34B", - "version": "1.0", - "description": "Dolphin, based on the Yi-34B model and enhanced with features like conversation and empathy, is trained on a unique dataset for advanced multi-turn conversations. Notably uncensored, it requires careful implementation of an alignment layer for ethical use.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "<|im_start|>system\n", - "user_prompt": "<|im_end|>\n<|im_start|>user\n", - "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "ehartford, The Bloke", - "tags": ["General Use", "Role-playing"], - "size": 24320000000 - } - } - \ No newline at end of file diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index 4aaee79b3..166e3d8ed 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "lizpreciatior, The Bloke", - "tags": ["General Use", "Role-playing"], + "tags": ["Community Recommended", "General Use", "Role-playing"], "size": 48750000000 } } From 5cc8e0f9a9c5cc16ea2769a5337b9cab56f2ea87 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 11:50:56 +0700 Subject: [PATCH 014/179] decapriate some small models --- models/capybara-34b/model.json | 4 ++-- models/deepseek-coder-1.3b/model.json | 2 +- models/islm-3b/model.json | 24 ------------------------ models/marx-3b/model.json | 23 ----------------------- models/mythomax-13b/model.json | 24 ------------------------ 5 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 models/islm-3b/model.json delete mode 100644 models/marx-3b/model.json delete mode 100644 models/mythomax-13b/model.json diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index 562bcbe93..d2da8d002 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -3,7 +3,7 @@ "id": "capybara-34b", "object": "model", "name": "Capybara 200k 34B", - "version": 1.0, + "version": "1.0", "description": "Nous Capybara 34B, a variant of the Yi-34B model, is the first Nous model with a 200K context length, trained for three epochs on the innovative Capybara dataset.", "format": "gguf", "settings": { @@ -17,7 +17,7 @@ }, "metadata": { "author": "NousResearch, The Bloke", - "tags": ["General", "Big Context Length"], + "tags": ["Community Recommended", "General Use", "Big Context Length"], "size": 24320000000 } } diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index 2ff6d6e7b..3ee705d20 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "deepseek, The Bloke", - "tags": ["Code"], + "tags": ["Community Recommended", "Code", "Small size"], "size": 870000000 } } diff --git a/models/islm-3b/model.json b/models/islm-3b/model.json deleted file mode 100644 index 916d7c50e..000000000 --- a/models/islm-3b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/UmbrellaCorp/IS-LM-3B_GGUF/resolve/main/IS-LM-Q4_K_M.gguf", - "id": "islm-3b", - "object": "model", - "name": "IS LM 3B", - "version": "1.0", - "description": "IS LM 3B, based on the StableLM 3B model is specifically finetuned for economic analysis using DataForge Economics and QLoRA over three epochs, enhancing its proficiency in economic forecasting and analysis.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "USER: ", - "ai_prompt": "ASSISTANT: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "UmbrellaCorp, The Bloke", - "tags": ["General Use", "Economics"], - "size": 1710000000 - } - } - \ No newline at end of file diff --git a/models/marx-3b/model.json b/models/marx-3b/model.json deleted file mode 100644 index 78617d5c3..000000000 --- a/models/marx-3b/model.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/Marx-3B-v3-GGUF/resolve/main/marx-3b-v3.Q4_K_M.gguf", - "id": "marx-3b", - "object": "model", - "name": "Marx 3B", - "version": "1.0", - "description": "Marx 3B, based on the StableLM 3B model is specifically finetuned for chating using EverythingLM data and QLoRA over two epochs, enhancing its proficiency in understand general knowledege.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "### HUMAN: ", - "ai_prompt": "### RESPONSE: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "Bohan Du, The Bloke", - "tags": ["General Use"], - "size": 1620000000 - } - } \ No newline at end of file diff --git a/models/mythomax-13b/model.json b/models/mythomax-13b/model.json deleted file mode 100644 index 455f73968..000000000 --- a/models/mythomax-13b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/MythoMax-L2-13B-GGUF/resolve/main/mythomax-l2-13b.Q5_K_M.gguf", - "id": "mythomax-13b", - "object": "model", - "name": "Mythomax L2 13B", - "version": "1.0", - "description": "Mythomax L2 13b, an advanced AI model derived from MythoMix, merges MythoLogic-L2's deep comprehension with Huginn's writing skills through a unique tensor merge technique, excelling in roleplaying and storytelling.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "### Instruction: ", - "ai_prompt": "### Response: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "Gryphe, The Bloke", - "tags": ["Role-playing"], - "size": 9230000000 - } - } - \ No newline at end of file From 4440b58b15668b6538b87fa75b500e0d5c299815 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 11:52:34 +0700 Subject: [PATCH 015/179] decapriate some 7B models --- models/openchat-7b/model.json | 24 ------------------------ models/openhermes-mistral-7b/model.json | 24 ------------------------ models/openorca-7b/model.json | 24 ------------------------ 3 files changed, 72 deletions(-) delete mode 100644 models/openchat-7b/model.json delete mode 100644 models/openhermes-mistral-7b/model.json delete mode 100644 models/openorca-7b/model.json diff --git a/models/openchat-7b/model.json b/models/openchat-7b/model.json deleted file mode 100644 index 1fd6bb259..000000000 --- a/models/openchat-7b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/openchat_3.5-GGUF/resolve/main/openchat_3.5.Q4_K_M.gguf", - "id": "openchat-7b", - "object": "model", - "name": "Open Chat 3.5 7B", - "version": "1.0", - "description": "OpenChat represents a breakthrough in the realm of open-source language models. By implementing the C-RLFT fine-tuning strategy, inspired by offline reinforcement learning, this 7B model achieves results on par with ChatGPT (March).", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "", - "user_prompt": "GPT4 User: ", - "ai_prompt": "<|end_of_turn|>\nGPT4 Assistant: " - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "OpenChat, The Bloke", - "tags": ["General", "Code"], - "size": 4370000000 - } - } - \ No newline at end of file diff --git a/models/openhermes-mistral-7b/model.json b/models/openhermes-mistral-7b/model.json deleted file mode 100644 index 6b64363d5..000000000 --- a/models/openhermes-mistral-7b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/OpenHermes-2.5-Mistral-7B-GGUF/resolve/main/openhermes-2.5-mistral-7b.Q4_K_M.gguf", - "id": "openhermes-mistral-7b", - "object": "model", - "name": "Openhermes 2.5 Mistral 7B", - "version": "1.0", - "description": "The OpenHermes 2.5 Mistral 7B incorporates additional code datasets, more than a million GPT-4 generated data examples, and other high-quality open datasets. This enhancement led to significant improvement in benchmarks, highlighting its improved skill in handling code-centric tasks.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "<|im_start|>system\n", - "user_prompt": "<|im_end|>\n<|im_start|>user\n", - "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "Teknium, The Bloke", - "tags": ["General", "Roleplay"], - "size": 4370000000 - } - } - \ No newline at end of file diff --git a/models/openorca-7b/model.json b/models/openorca-7b/model.json deleted file mode 100644 index 42c88212c..000000000 --- a/models/openorca-7b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/Mistral-7B-OpenOrca-GGUF/resolve/main/mistral-7b-openorca.Q4_K_M.gguf", - "id": "openorca-7b", - "object": "model", - "name": "OpenOrca 7B", - "version": "1.0", - "description": "OpenOrca 8k 7B is a model based on Mistral 7B, fine-tuned using the OpenOrca dataset. Notably ranked first on the HF Leaderboard for models under 30B, it excels in efficiency and accessibility.", - "format": "gguf", - "settings": { - "ctx_len": 4096, - "system_prompt": "<|im_start|>system\n", - "user_prompt": "<|im_end|>\n<|im_start|>user\n", - "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" - }, - "parameters": { - "max_tokens": 4096 - }, - "metadata": { - "author": "OpenOrca, The Bloke", - "tags": ["General", "Code"], - "size": 4370000000 - } - } - \ No newline at end of file From c27a1cc3796e86b2afe536a48c75c53459e6d5c3 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 11:56:39 +0700 Subject: [PATCH 016/179] update tags for models --- models/neural-chat-7b/model.json | 2 +- models/neuralhermes-7b/model.json | 2 +- models/noromaid-20b/model.json | 2 +- models/openorca-13b/model.json | 2 +- models/phind-34b/model.json | 2 +- models/rocket-3b/model.json | 2 +- models/starling-7b/model.json | 2 +- models/tiefighter-13b/model.json | 2 +- models/tinyllama-1.1b/model.json | 2 +- models/wizardcoder-13b/model.json | 2 +- models/wizardcoder-34b/model.json | 2 +- models/yi-34b/model.json | 2 +- models/zephyr-beta-7b/model.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index f4f4f14d4..a31662496 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["General Use", "Role-playing", "Big Context Length"], + "tags": ["Community Recommended", "General Use", "Role-playing", "Big Context Length"], "size": 4370000000 } } diff --git a/models/neuralhermes-7b/model.json b/models/neuralhermes-7b/model.json index 07cca58d4..3ec0c6253 100644 --- a/models/neuralhermes-7b/model.json +++ b/models/neuralhermes-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["General Use", "Code", "Big Context Length"], + "tags": ["Community Recommended", "General Use", "Code", "Big Context Length"], "size": 4370000000 } } diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index 86291e4f5..f0460568c 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "NeverSleep, The Bloke", - "tags": ["Role-playing"], + "tags": ["Community Recommended", "Role-playing"], "size": 12040000000 } } diff --git a/models/openorca-13b/model.json b/models/openorca-13b/model.json index 02a555430..5027dbd22 100644 --- a/models/openorca-13b/model.json +++ b/models/openorca-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Microsoft, The Bloke", - "tags": ["General Use"], + "tags": ["Community Recommended", "General Use"], "size": 9230000000 } } diff --git a/models/phind-34b/model.json b/models/phind-34b/model.json index 4391ae08d..18ed50bcc 100644 --- a/models/phind-34b/model.json +++ b/models/phind-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Phind, The Bloke", - "tags": ["Code", "Big Context Length"], + "tags": ["Community Recommended", "Code", "Big Context Length"], "size": 24320000000 } } diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index b00eb1f44..4eddb04b0 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "pansophic, The Bloke", - "tags": ["General Use"], + "tags": ["Community Recommended", "General Use"], "size": 1710000000 } } \ No newline at end of file diff --git a/models/starling-7b/model.json b/models/starling-7b/model.json index c029ea7d9..4ff5651e0 100644 --- a/models/starling-7b/model.json +++ b/models/starling-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Berkeley-nest, The Bloke", - "tags": ["General", "Code"], + "tags": ["Community Recommended", "General", "Code"], "size": 4370000000 } } diff --git a/models/tiefighter-13b/model.json b/models/tiefighter-13b/model.json index b1d354ce3..ea38fadbf 100644 --- a/models/tiefighter-13b/model.json +++ b/models/tiefighter-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "KoboldAI, The Bloke", - "tags": ["General Use", "Role-playing", "Writing"], + "tags": ["Community Recommended", "General Use", "Role-playing", "Writing"], "size": 9230000000 } } diff --git a/models/tinyllama-1.1b/model.json b/models/tinyllama-1.1b/model.json index f561eb25d..6f10fde02 100644 --- a/models/tinyllama-1.1b/model.json +++ b/models/tinyllama-1.1b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "TinyLlama", - "tags": ["General Use"], + "tags": ["Community Recommended", "General Use"], "size": 637000000 } } \ No newline at end of file diff --git a/models/wizardcoder-13b/model.json b/models/wizardcoder-13b/model.json index 944b5632b..9fe250348 100644 --- a/models/wizardcoder-13b/model.json +++ b/models/wizardcoder-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "WizardLM, The Bloke", - "tags": ["Code", "Big Context Length"], + "tags": ["Community Recommended", "Code", "Big Context Length"], "size": 9230000000 } } diff --git a/models/wizardcoder-34b/model.json b/models/wizardcoder-34b/model.json index aa2618e1b..db92a4fbf 100644 --- a/models/wizardcoder-34b/model.json +++ b/models/wizardcoder-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "WizardLM, The Bloke", - "tags": ["Code", "Big Context Length"], + "tags": ["Community Recommended", "Code", "Big Context Length"], "size": 24320000000 } } diff --git a/models/yi-34b/model.json b/models/yi-34b/model.json index f899bc54b..9bbe65760 100644 --- a/models/yi-34b/model.json +++ b/models/yi-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "01-ai, The Bloke", - "tags": ["General", "Role-playing", "Writing"], + "tags": ["Foundational Model", "General Use", "Role-playing", "Writing"], "size": 24320000000 } } diff --git a/models/zephyr-beta-7b/model.json b/models/zephyr-beta-7b/model.json index 24529bc9a..474dba766 100644 --- a/models/zephyr-beta-7b/model.json +++ b/models/zephyr-beta-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "HuggingFaceH4, The Bloke", - "tags": ["General Use", "Big Context Length"], + "tags": ["Community Recommended", "General Use", "Big Context Length"], "size": 4370000000 } } From 6a5458731991866cc2dafffcbbab9477d2283cf0 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 12:26:50 +0700 Subject: [PATCH 017/179] update llama 2 7b chat --- models/capybara-34b/model.json | 4 ++-- models/deepseek-coder-1.3b/model.json | 4 ++-- models/llama2-chat-7b-q4/model.json | 24 ++++++++++++++++++++++++ models/llama2-chat-7b-q5/model.json | 24 ++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 models/llama2-chat-7b-q4/model.json create mode 100644 models/llama2-chat-7b-q5/model.json diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index d2da8d002..7c669bbb7 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -7,13 +7,13 @@ "description": "Nous Capybara 34B, a variant of the Yi-34B model, is the first Nous model with a 200K context length, trained for three epochs on the innovative Capybara dataset.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "USER: ", "ai_prompt": "ASSISTANT: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "NousResearch, The Bloke", diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index 3ee705d20..dac90423d 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -7,13 +7,13 @@ "description": "", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "", "ai_prompt": "" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "deepseek, The Bloke", diff --git a/models/llama2-chat-7b-q4/model.json b/models/llama2-chat-7b-q4/model.json new file mode 100644 index 000000000..be302d144 --- /dev/null +++ b/models/llama2-chat-7b-q4/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q4_K_M.gguf", + "id": "llama2-chat-7b-q4", + "object": "model", + "name": "Llama 2 Chat 7B Q4", + "version": "1.0", + "description": "This is a 4-bit quantized version of Meta AI's Llama 2 Chat 7b model.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "[INST] <>\n", + "user_prompt": "<>\n", + "ai_prompt": "[/INST]" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "MetaAI, The Bloke", + "tags": ["Foundational Model", "General", "Code"], + "size": 4080000000 + } + } + \ No newline at end of file diff --git a/models/llama2-chat-7b-q5/model.json b/models/llama2-chat-7b-q5/model.json new file mode 100644 index 000000000..8a93327d6 --- /dev/null +++ b/models/llama2-chat-7b-q5/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q5_K_M.gguf", + "id": "llama2-chat-7b-q5", + "object": "model", + "name": "Llama 2 Chat 7B Q5", + "version": "1.0", + "description": "This is a 5-bit quantized version of Meta AI's Llama 2 Chat 7b model.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "[INST] <>\n", + "user_prompt": "<>\n", + "ai_prompt": "[/INST]" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "MetaAI, The Bloke", + "tags": ["Foundational Model", "General", "Code"], + "size": 4780000000 + } + } + \ No newline at end of file From dbc12d27c89f31e1939150344cac57885c8025ee Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 12:32:56 +0700 Subject: [PATCH 018/179] update mistral 7b chat --- models/lzlv-70b/model.json | 4 ++-- models/mistral-ins-7b-q4/model.json | 24 ++++++++++++++++++++++++ models/mistral-ins-7b-q5/model.json | 24 ++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 models/mistral-ins-7b-q4/model.json create mode 100644 models/mistral-ins-7b-q5/model.json diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index 166e3d8ed..98d581f24 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -7,13 +7,13 @@ "description": "lzlv_70B is a sophisticated AI model designed for roleplaying and creative tasks. This merge aims to combine intelligence with creativity, seemingly outperforming its individual components in complex scenarios and creative outputs.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "USER: ", "ai_prompt": "ASSISTANT: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "lizpreciatior, The Bloke", diff --git a/models/mistral-ins-7b-q4/model.json b/models/mistral-ins-7b-q4/model.json new file mode 100644 index 000000000..51b5c231e --- /dev/null +++ b/models/mistral-ins-7b-q4/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.1-GGUF/resolve/main/mistral-7b-instruct-v0.1.Q4_K_M.gguf", + "id": "mistral-ins-7b-q4", + "object": "model", + "name": "Mistral Instruct 7B Q4", + "version": "1.0", + "description": "This is a 4-bit quantized version of MistralAI's Mistral Instruct 7B model.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "", + "user_prompt": "[INST]", + "ai_prompt": "[/INST]" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "MistralAI, The Bloke", + "tags": ["Foundational Model", "General", "Code"], + "size": 4370000000 + } + } + \ No newline at end of file diff --git a/models/mistral-ins-7b-q5/model.json b/models/mistral-ins-7b-q5/model.json new file mode 100644 index 000000000..46c5f5378 --- /dev/null +++ b/models/mistral-ins-7b-q5/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.1-GGUF/resolve/main/mistral-7b-instruct-v0.1.Q5_K_M.gguf", + "id": "mistral-ins-7b-q5", + "object": "model", + "name": "Mistral Instruct 7B Q5", + "version": "1.0", + "description": "This is a 5-bit quantized version of MistralAI's Mistral Instruct 7B model.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "", + "user_prompt": "[INST]", + "ai_prompt": "[/INST]" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "MistralAI, The Bloke", + "tags": ["Foundational Model", "General", "Code"], + "size": 5130000000 + } + } + \ No newline at end of file From 88e37e1f2d6d3b0c0e0f91bcf615233a2ccdbd2f Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 12:34:10 +0700 Subject: [PATCH 019/179] update ctxlen --- models/neural-chat-7b/model.json | 4 ++-- models/neuralhermes-7b/model.json | 4 ++-- models/noromaid-20b/model.json | 4 ++-- models/openorca-13b/model.json | 4 ++-- models/phind-34b/model.json | 4 ++-- models/rocket-3b/model.json | 2 +- models/starling-7b/model.json | 4 ++-- models/tiefighter-13b/model.json | 4 ++-- models/wizardcoder-13b/model.json | 4 ++-- models/wizardcoder-34b/model.json | 4 ++-- models/yi-34b/model.json | 4 ++-- models/zephyr-beta-7b/model.json | 4 ++-- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index a31662496..84c8d029f 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -7,13 +7,13 @@ "description": "The Neural Chat 7B model, developed on the foundation of mistralai/Mistral-7B-v0.1, has been fine-tuned using the Open-Orca/SlimOrca dataset and aligned with the Direct Preference Optimization (DPO) algorithm. It has demonstrated substantial improvements in various AI tasks and performance well on the open_llm_leaderboard.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "### System: ", "user_prompt": "### User: ", "ai_prompt": "### Assistant: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "Intel, The Bloke", diff --git a/models/neuralhermes-7b/model.json b/models/neuralhermes-7b/model.json index 3ec0c6253..0cc83d7fb 100644 --- a/models/neuralhermes-7b/model.json +++ b/models/neuralhermes-7b/model.json @@ -7,13 +7,13 @@ "description": "NeuralHermes 2.5 has been enhanced using Direct Preference Optimization. This fine-tuning, inspired by the RLHF process of Neural-chat-7b and OpenHermes-2.5-Mistral-7B, has led to improved performance across several benchmarks.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "<|im_start|>system\n", "user_prompt": "<|im_end|>\n<|im_start|>user\n", "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "Intel, The Bloke", diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index f0460568c..ff2b91c1d 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -7,13 +7,13 @@ "description": "The Noromaid 20b model is designed for role-playing and general use, featuring a unique touch with the no_robots dataset that enhances human-like behavior.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "### Instruction: ", "ai_prompt": "### Response: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "NeverSleep, The Bloke", diff --git a/models/openorca-13b/model.json b/models/openorca-13b/model.json index 5027dbd22..13b6abf29 100644 --- a/models/openorca-13b/model.json +++ b/models/openorca-13b/model.json @@ -7,13 +7,13 @@ "description": "Orca 2 is a finetuned version of LLAMA-2, designed primarily for single-turn responses in reasoning, reading comprehension, math problem solving, and text summarization.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "<|im_start|>system\n", "user_prompt": "<|im_end|>\n<|im_start|>user\n", "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "Microsoft, The Bloke", diff --git a/models/phind-34b/model.json b/models/phind-34b/model.json index 18ed50bcc..761c51346 100644 --- a/models/phind-34b/model.json +++ b/models/phind-34b/model.json @@ -7,13 +7,13 @@ "description": "Phind-CodeLlama-34B-v2 is an AI model fine-tuned on 1.5B tokens of high-quality programming data. It's a SOTA open-source model in coding. This multi-lingual model excels in various programming languages, including Python, C/C++, TypeScript, Java, and is designed to be steerable and user-friendly.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "### System Prompt\n", "user_prompt": "### User Message\n", "ai_prompt": "### Assistant\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "Phind, The Bloke", diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index 4eddb04b0..a70db1dbd 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -13,7 +13,7 @@ "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "pansophic, The Bloke", diff --git a/models/starling-7b/model.json b/models/starling-7b/model.json index 4ff5651e0..cf8aeda18 100644 --- a/models/starling-7b/model.json +++ b/models/starling-7b/model.json @@ -7,13 +7,13 @@ "description": "Starling-RM-7B-alpha is a language model finetuned with Reinforcement Learning from AI Feedback from Openchat 3.5. It stands out for its impressive performance using GPT-4 as a judge, making it one of the top-performing models in its category.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "GPT4 User: ", "ai_prompt": "<|end_of_turn|>\nGPT4 Assistant: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "Berkeley-nest, The Bloke", diff --git a/models/tiefighter-13b/model.json b/models/tiefighter-13b/model.json index ea38fadbf..4dd9bb37f 100644 --- a/models/tiefighter-13b/model.json +++ b/models/tiefighter-13b/model.json @@ -7,13 +7,13 @@ "description": "Tiefighter-13B is a highly creative, merged AI model achieved by combining various 'LORAs' on top of an existing merge, particularly focusing on storytelling and improvisation. This model excels in story writing, chatbots, and adventuring, and is designed to perform better with less detailed inputs, leveraging its inherent creativity.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "### Instruction: ", "ai_prompt": "\n### Response: " }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "KoboldAI, The Bloke", diff --git a/models/wizardcoder-13b/model.json b/models/wizardcoder-13b/model.json index 9fe250348..47899534a 100644 --- a/models/wizardcoder-13b/model.json +++ b/models/wizardcoder-13b/model.json @@ -7,13 +7,13 @@ "description": "WizardCoder-Python-13B is a Python coding model major models like ChatGPT-3.5. This model based on the Llama2 architecture, demonstrate high proficiency in specific domains like coding and mathematics.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "### Instruction:\n", "ai_prompt": "### Response:\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "WizardLM, The Bloke", diff --git a/models/wizardcoder-34b/model.json b/models/wizardcoder-34b/model.json index db92a4fbf..f564afc35 100644 --- a/models/wizardcoder-34b/model.json +++ b/models/wizardcoder-34b/model.json @@ -7,13 +7,13 @@ "description": "WizardCoder-Python-34B is a Python coding model major models like ChatGPT-3.5. This model based on the Llama2 architecture, demonstrate high proficiency in specific domains like coding and mathematics.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "", "user_prompt": "### Instruction:\n", "ai_prompt": "### Response:\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "WizardLM, The Bloke", diff --git a/models/yi-34b/model.json b/models/yi-34b/model.json index 9bbe65760..d34c0efed 100644 --- a/models/yi-34b/model.json +++ b/models/yi-34b/model.json @@ -7,13 +7,13 @@ "description": "Yi-34B, a specialized chat model, is known for its diverse and creative responses and excels across various NLP tasks and benchmarks.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "<|im_start|>system\n", "user_prompt": "<|im_end|>\n<|im_start|>user\n", "ai_prompt": "<|im_end|>\n<|im_start|>assistant\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "01-ai, The Bloke", diff --git a/models/zephyr-beta-7b/model.json b/models/zephyr-beta-7b/model.json index 474dba766..70c7c72e8 100644 --- a/models/zephyr-beta-7b/model.json +++ b/models/zephyr-beta-7b/model.json @@ -7,13 +7,13 @@ "description": "The Zephyr-7B-β model marks the second iteration in the Zephyr series, designed to function as an effective assistant. It has been fine-tuned from the mistralai/Mistral-7B-v0.1 base model, utilizing a combination of public and synthetic datasets with the application of Direct Preference Optimization.", "format": "gguf", "settings": { - "ctx_len": 4096, + "ctx_len": 2048, "system_prompt": "<|system|>\n", "user_prompt": "\n<|user|>\n", "ai_prompt": "\n<|assistant|>\n" }, "parameters": { - "max_tokens": 4096 + "max_tokens": 2048 }, "metadata": { "author": "HuggingFaceH4, The Bloke", From fc2af712e405d977d33f2873ec6f18ddb1062938 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 12:35:53 +0700 Subject: [PATCH 020/179] update llama2 70b chat --- models/llama2-chat-70b-q4/model.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 models/llama2-chat-70b-q4/model.json diff --git a/models/llama2-chat-70b-q4/model.json b/models/llama2-chat-70b-q4/model.json new file mode 100644 index 000000000..39c0f6474 --- /dev/null +++ b/models/llama2-chat-70b-q4/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/Llama-2-70B-Chat-GGUF/resolve/main/llama-2-70b-chat.Q4_K_M.gguf", + "id": "llama2-chat-70b-q4", + "object": "model", + "name": "Llama 2 Chat 70B Q4", + "version": "1.0", + "description": "This is a 4-bit quantized version of Meta AI's Llama 2 Chat 70b model.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "[INST] <>\n", + "user_prompt": "<>\n", + "ai_prompt": "[/INST]" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "MetaAI, The Bloke", + "tags": ["Foundational Model", "General", "Code"], + "size": 4080000000 + } + } + \ No newline at end of file From 43b538e800a7469a3f25eceab1d97e8a7eb61c90 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 14:13:00 +0700 Subject: [PATCH 021/179] update openhermes 7b --- models/capybara-34b/model.json | 4 ++-- models/lzlv-70b/model.json | 4 ++-- models/neural-chat-7b/model.json | 6 +++--- .../{neuralhermes-7b => openhermes-neural-7b}/model.json | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) rename models/{neuralhermes-7b => openhermes-neural-7b}/model.json (52%) diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index 7c669bbb7..74eb27eb9 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -9,8 +9,8 @@ "settings": { "ctx_len": 2048, "system_prompt": "", - "user_prompt": "USER: ", - "ai_prompt": "ASSISTANT: " + "user_prompt": "USER:\n", + "ai_prompt": "ASSISTANT:\n" }, "parameters": { "max_tokens": 2048 diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index 98d581f24..844bdadf4 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -9,8 +9,8 @@ "settings": { "ctx_len": 2048, "system_prompt": "", - "user_prompt": "USER: ", - "ai_prompt": "ASSISTANT: " + "user_prompt": "USER:\n", + "ai_prompt": "ASSISTANT:\n" }, "parameters": { "max_tokens": 2048 diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index 84c8d029f..cac717b8a 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -8,9 +8,9 @@ "format": "gguf", "settings": { "ctx_len": 2048, - "system_prompt": "### System: ", - "user_prompt": "### User: ", - "ai_prompt": "### Assistant: " + "system_prompt": "### System:\n", + "user_prompt": "### User:\n", + "ai_prompt": "### Assistant:\n" }, "parameters": { "max_tokens": 2048 diff --git a/models/neuralhermes-7b/model.json b/models/openhermes-neural-7b/model.json similarity index 52% rename from models/neuralhermes-7b/model.json rename to models/openhermes-neural-7b/model.json index 0cc83d7fb..cd4394dc5 100644 --- a/models/neuralhermes-7b/model.json +++ b/models/openhermes-neural-7b/model.json @@ -1,10 +1,10 @@ { - "source_url": "https://huggingface.co/TheBloke/NeuralHermes-2.5-Mistral-7B-GGUF/resolve/main/neuralhermes-2.5-mistral-7b.Q4_K_M.gguf", - "id": "neuralhermes-7b", + "source_url": "https://huggingface.co/TheBloke/OpenHermes-2.5-neural-chat-7B-v3-2-7B-GGUF/resolve/main/openhermes-2.5-neural-chat-7b-v3-2-7b.Q4_K_M.gguf", + "id": "openhermes-neural-7b", "object": "model", - "name": "NeuralHermes 7B", + "name": "OpenHermes Neural 7B", "version": "1.0", - "description": "NeuralHermes 2.5 has been enhanced using Direct Preference Optimization. This fine-tuning, inspired by the RLHF process of Neural-chat-7b and OpenHermes-2.5-Mistral-7B, has led to improved performance across several benchmarks.", + "description": "OpenHermes Neural is a merged model from OpenHermes-2.5-Mistral-7B and neural-chat-7b-v3-2 with the TIES method.", "format": "gguf", "settings": { "ctx_len": 2048, From 23a6ecdd890be73e04d0ffe990fc81e9b06d8fe0 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 14:14:43 +0700 Subject: [PATCH 022/179] update tags --- models/openhermes-neural-7b/model.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/openhermes-neural-7b/model.json b/models/openhermes-neural-7b/model.json index cd4394dc5..e9e26706c 100644 --- a/models/openhermes-neural-7b/model.json +++ b/models/openhermes-neural-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["Community Recommended", "General Use", "Code", "Big Context Length"], + "tags": ["Community Recommended", "General Use", "Code", "Merged"], "size": 4370000000 } } From 84c940be47d118759a129b66ff80eb2cae1659c2 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 14:28:36 +0700 Subject: [PATCH 023/179] update prompt templte --- models/noromaid-20b/model.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index ff2b91c1d..61870e855 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -9,8 +9,8 @@ "settings": { "ctx_len": 2048, "system_prompt": "", - "user_prompt": "### Instruction: ", - "ai_prompt": "### Response: " + "user_prompt": "### Instruction:\n", + "ai_prompt": "### Response:\n" }, "parameters": { "max_tokens": 2048 From 7017e6426d4ea022a6bc6197c531e3560fc3767a Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 16:22:42 +0700 Subject: [PATCH 024/179] update name for models --- models/capybara-34b/model.json | 2 +- models/deepseek-coder-1.3b/model.json | 4 ++-- models/llama2-chat-70b-q4/model.json | 2 +- models/llama2-chat-7b-q4/model.json | 2 +- models/llama2-chat-7b-q5/model.json | 2 +- models/lzlv-70b/model.json | 2 +- models/mistral-ins-7b-q4/model.json | 2 +- models/mistral-ins-7b-q5/model.json | 2 +- models/neural-chat-7b/model.json | 2 +- models/noromaid-20b/model.json | 2 +- models/openhermes-neural-7b/model.json | 2 +- models/openorca-13b/model.json | 2 +- models/phind-34b/model.json | 2 +- models/rocket-3b/model.json | 2 +- models/starling-7b/model.json | 2 +- models/tiefighter-13b/model.json | 2 +- models/tinyllama-1.1b/model.json | 2 +- models/wizardcoder-13b/model.json | 2 +- models/wizardcoder-34b/model.json | 2 +- models/yi-34b/model.json | 2 +- models/zephyr-beta-7b/model.json | 4 ++-- 21 files changed, 23 insertions(+), 23 deletions(-) diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index 74eb27eb9..e5834a66d 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Nous-Capybara-34B-GGUF/resolve/main/nous-capybara-34b.Q5_K_M.gguf", "id": "capybara-34b", "object": "model", - "name": "Capybara 200k 34B", + "name": "Capybara 200k 34B Q5", "version": "1.0", "description": "Nous Capybara 34B, a variant of the Yi-34B model, is the first Nous model with a 200K context length, trained for three epochs on the innovative Capybara dataset.", "format": "gguf", diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index dac90423d..9d22f9558 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/deepseek-coder-1.3b-base-GGUF/resolve/main/deepseek-coder-1.3b-base.Q4_K_M.gguf", "id": "deepseek-coder-1.3b", "object": "model", - "name": "Deepseek Coder 1.3B", + "name": "Deepseek Coder 1.3B Q4", "version": "1.0", "description": "", "format": "gguf", @@ -16,7 +16,7 @@ "max_tokens": 2048 }, "metadata": { - "author": "deepseek, The Bloke", + "author": "Deepseek, The Bloke", "tags": ["Community Recommended", "Code", "Small size"], "size": 870000000 } diff --git a/models/llama2-chat-70b-q4/model.json b/models/llama2-chat-70b-q4/model.json index 39c0f6474..4ab3ac555 100644 --- a/models/llama2-chat-70b-q4/model.json +++ b/models/llama2-chat-70b-q4/model.json @@ -18,7 +18,7 @@ "metadata": { "author": "MetaAI, The Bloke", "tags": ["Foundational Model", "General", "Code"], - "size": 4080000000 + "size": 43920000000 } } \ No newline at end of file diff --git a/models/llama2-chat-7b-q4/model.json b/models/llama2-chat-7b-q4/model.json index be302d144..7b52e1767 100644 --- a/models/llama2-chat-7b-q4/model.json +++ b/models/llama2-chat-7b-q4/model.json @@ -4,7 +4,7 @@ "object": "model", "name": "Llama 2 Chat 7B Q4", "version": "1.0", - "description": "This is a 4-bit quantized version of Meta AI's Llama 2 Chat 7b model.", + "description": "This is a 4-bit quantized iteration of Meta AI's Llama 2 Chat 7b model, specifically designed for a comprehensive understanding through training on extensive internet data.", "format": "gguf", "settings": { "ctx_len": 2048, diff --git a/models/llama2-chat-7b-q5/model.json b/models/llama2-chat-7b-q5/model.json index 8a93327d6..b19213e46 100644 --- a/models/llama2-chat-7b-q5/model.json +++ b/models/llama2-chat-7b-q5/model.json @@ -4,7 +4,7 @@ "object": "model", "name": "Llama 2 Chat 7B Q5", "version": "1.0", - "description": "This is a 5-bit quantized version of Meta AI's Llama 2 Chat 7b model.", + "description": "This is a 5-bit quantized iteration of Meta AI's Llama 2 Chat 7b model, specifically designed for a comprehensive understanding through training on extensive internet data.", "format": "gguf", "settings": { "ctx_len": 2048, diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index 844bdadf4..e4247eb88 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/lzlv_70B-GGUF/resolve/main/lzlv_70b_fp16_hf.Q5_K_M.gguf", "id": "lzlv-70b", "object": "model", - "name": "Lzlv 70B", + "name": "Lzlv 70B Q4", "version": "1.0", "description": "lzlv_70B is a sophisticated AI model designed for roleplaying and creative tasks. This merge aims to combine intelligence with creativity, seemingly outperforming its individual components in complex scenarios and creative outputs.", "format": "gguf", diff --git a/models/mistral-ins-7b-q4/model.json b/models/mistral-ins-7b-q4/model.json index 51b5c231e..dd3360700 100644 --- a/models/mistral-ins-7b-q4/model.json +++ b/models/mistral-ins-7b-q4/model.json @@ -4,7 +4,7 @@ "object": "model", "name": "Mistral Instruct 7B Q4", "version": "1.0", - "description": "This is a 4-bit quantized version of MistralAI's Mistral Instruct 7B model.", + "description": "This is a 4-bit quantized iteration of MistralAI's Mistral Instruct 7b model, specifically designed for a comprehensive understanding through training on extensive internet data.", "format": "gguf", "settings": { "ctx_len": 2048, diff --git a/models/mistral-ins-7b-q5/model.json b/models/mistral-ins-7b-q5/model.json index 46c5f5378..5b86ab538 100644 --- a/models/mistral-ins-7b-q5/model.json +++ b/models/mistral-ins-7b-q5/model.json @@ -4,7 +4,7 @@ "object": "model", "name": "Mistral Instruct 7B Q5", "version": "1.0", - "description": "This is a 5-bit quantized version of MistralAI's Mistral Instruct 7B model.", + "description": "This is a 5-bit quantized iteration of MistralAI's Mistral Instruct 7b model, specifically designed for a comprehensive understanding through training on extensive internet data.", "format": "gguf", "settings": { "ctx_len": 2048, diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index cac717b8a..234a2ff80 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/neural-chat-7B-v3-1-GGUF/resolve/main/neural-chat-7b-v3-1.Q4_K_M.gguf", "id": "neural-chat-7b", "object": "model", - "name": "Neural Chat 7B", + "name": "Neural Chat 7B Q4", "version": "1.0", "description": "The Neural Chat 7B model, developed on the foundation of mistralai/Mistral-7B-v0.1, has been fine-tuned using the Open-Orca/SlimOrca dataset and aligned with the Direct Preference Optimization (DPO) algorithm. It has demonstrated substantial improvements in various AI tasks and performance well on the open_llm_leaderboard.", "format": "gguf", diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index 61870e855..da69cba3e 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Noromaid-20B-v0.1.1-GGUF/resolve/main/noromaid-20b-v0.1.1.Q4_K_M.gguf", "id": "noromaid-20b", "object": "model", - "name": "Noromaid 20B", + "name": "Noromaid 20B Q4", "version": "1.0", "description": "The Noromaid 20b model is designed for role-playing and general use, featuring a unique touch with the no_robots dataset that enhances human-like behavior.", "format": "gguf", diff --git a/models/openhermes-neural-7b/model.json b/models/openhermes-neural-7b/model.json index e9e26706c..a7366f85b 100644 --- a/models/openhermes-neural-7b/model.json +++ b/models/openhermes-neural-7b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/OpenHermes-2.5-neural-chat-7B-v3-2-7B-GGUF/resolve/main/openhermes-2.5-neural-chat-7b-v3-2-7b.Q4_K_M.gguf", "id": "openhermes-neural-7b", "object": "model", - "name": "OpenHermes Neural 7B", + "name": "OpenHermes Neural 7B Q4", "version": "1.0", "description": "OpenHermes Neural is a merged model from OpenHermes-2.5-Mistral-7B and neural-chat-7b-v3-2 with the TIES method.", "format": "gguf", diff --git a/models/openorca-13b/model.json b/models/openorca-13b/model.json index 13b6abf29..edfef0807 100644 --- a/models/openorca-13b/model.json +++ b/models/openorca-13b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Orca-2-13B-GGUF/resolve/main/orca-2-13b.Q5_K_M.gguf", "id": "openorca-13b", "object": "model", - "name": "Orca 2 13B", + "name": "Orca 2 13B Q5", "version": "1.0", "description": "Orca 2 is a finetuned version of LLAMA-2, designed primarily for single-turn responses in reasoning, reading comprehension, math problem solving, and text summarization.", "format": "gguf", diff --git a/models/phind-34b/model.json b/models/phind-34b/model.json index 761c51346..8de7a84d0 100644 --- a/models/phind-34b/model.json +++ b/models/phind-34b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Phind-CodeLlama-34B-v2-GGUF/resolve/main/phind-codellama-34b-v2.Q5_K_M.gguf", "id": "phind-34b", "object": "model", - "name": "Phind 34B", + "name": "Phind 34B Q5", "version": "1.0", "description": "Phind-CodeLlama-34B-v2 is an AI model fine-tuned on 1.5B tokens of high-quality programming data. It's a SOTA open-source model in coding. This multi-lingual model excels in various programming languages, including Python, C/C++, TypeScript, Java, and is designed to be steerable and user-friendly.", "format": "gguf", diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index a70db1dbd..7435bfafb 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/rocket-3B-GGUF/resolve/main/rocket-3b.Q4_K_M.gguf", "id": "rocket-3b", "object": "model", - "name": "Rocket 3B", + "name": "Rocket 3B Q4", "version": "1.0", "description": "Rocket-3B is a GPT-like model, primarily English, fine-tuned on diverse public datasets. It outperforms larger models in benchmarks, showcasing superior understanding and text generation, making it an effective chat model for its size.", "format": "gguf", diff --git a/models/starling-7b/model.json b/models/starling-7b/model.json index cf8aeda18..a7ba963fe 100644 --- a/models/starling-7b/model.json +++ b/models/starling-7b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Starling-LM-7B-alpha-GGUF/resolve/main/starling-lm-7b-alpha.Q4_K_M.gguf", "id": "starling-7b", "object": "model", - "name": "Strarling alpha 7B", + "name": "Strarling alpha 7B Q4", "version": "1.0", "description": "Starling-RM-7B-alpha is a language model finetuned with Reinforcement Learning from AI Feedback from Openchat 3.5. It stands out for its impressive performance using GPT-4 as a judge, making it one of the top-performing models in its category.", "format": "gguf", diff --git a/models/tiefighter-13b/model.json b/models/tiefighter-13b/model.json index 4dd9bb37f..9940c9a70 100644 --- a/models/tiefighter-13b/model.json +++ b/models/tiefighter-13b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/LLaMA2-13B-Tiefighter-GGUF/resolve/main/llama2-13b-tiefighter.Q5_K_M.gguf", "id": "tiefighter-13b", "object": "model", - "name": "Tiefighter 13B", + "name": "Tiefighter 13B Q5", "version": "1.0", "description": "Tiefighter-13B is a highly creative, merged AI model achieved by combining various 'LORAs' on top of an existing merge, particularly focusing on storytelling and improvisation. This model excels in story writing, chatbots, and adventuring, and is designed to perform better with less detailed inputs, leveraging its inherent creativity.", "format": "gguf", diff --git a/models/tinyllama-1.1b/model.json b/models/tinyllama-1.1b/model.json index 6f10fde02..92793d7f6 100644 --- a/models/tinyllama-1.1b/model.json +++ b/models/tinyllama-1.1b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TinyLlama/TinyLlama-1.1B-Chat-v0.6/resolve/main/ggml-model-q4_0.gguf", "id": "tinyllama-1.1b", "object": "model", - "name": "TinyLlama Chat 1.1B", + "name": "TinyLlama Chat 1.1B Q4", "version": "1.0", "description": "The TinyLlama project, featuring a 1.1B parameter Llama model, is pretrained on an expansive 3 trillion token dataset. Its design ensures easy integration with various Llama-based open-source projects. Despite its smaller size, it efficiently utilizes lower computational and memory resources, drawing on GPT-4's analytical prowess to enhance its conversational abilities and versatility.", "format": "gguf", diff --git a/models/wizardcoder-13b/model.json b/models/wizardcoder-13b/model.json index 47899534a..a7284aa7c 100644 --- a/models/wizardcoder-13b/model.json +++ b/models/wizardcoder-13b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/WizardCoder-Python-13B-V1.0-GGUF/resolve/main/wizardcoder-python-13b-v1.0.Q5_K_M.gguf", "id": "wizardcoder-13b", "object": "model", - "name": "Wizard Coder Python 13B", + "name": "Wizard Coder Python 13B Q5", "version": "1.0", "description": "WizardCoder-Python-13B is a Python coding model major models like ChatGPT-3.5. This model based on the Llama2 architecture, demonstrate high proficiency in specific domains like coding and mathematics.", "format": "gguf", diff --git a/models/wizardcoder-34b/model.json b/models/wizardcoder-34b/model.json index f564afc35..ca4a3da8e 100644 --- a/models/wizardcoder-34b/model.json +++ b/models/wizardcoder-34b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/WizardCoder-Python-34B-V1.0-GGUF/resolve/main/wizardcoder-python-34b-v1.0.Q5_K_M.gguf", "id": "wizardcoder-34b", "object": "model", - "name": "Wizard Coder Python 34B", + "name": "Wizard Coder Python 34B Q5", "version": "1.0", "description": "WizardCoder-Python-34B is a Python coding model major models like ChatGPT-3.5. This model based on the Llama2 architecture, demonstrate high proficiency in specific domains like coding and mathematics.", "format": "gguf", diff --git a/models/yi-34b/model.json b/models/yi-34b/model.json index d34c0efed..bc6e8e9e3 100644 --- a/models/yi-34b/model.json +++ b/models/yi-34b/model.json @@ -2,7 +2,7 @@ "source_url": "https://huggingface.co/TheBloke/Yi-34B-Chat-GGUF/resolve/main/yi-34b-chat.Q5_K_M.gguf", "id": "yi-34b", "object": "model", - "name": "Yi 34B", + "name": "Yi 34B Q5", "version": "1.0", "description": "Yi-34B, a specialized chat model, is known for its diverse and creative responses and excels across various NLP tasks and benchmarks.", "format": "gguf", diff --git a/models/zephyr-beta-7b/model.json b/models/zephyr-beta-7b/model.json index 70c7c72e8..e1a175432 100644 --- a/models/zephyr-beta-7b/model.json +++ b/models/zephyr-beta-7b/model.json @@ -2,9 +2,9 @@ "source_url": "https://huggingface.co/TheBloke/zephyr-7B-beta-GGUF/resolve/main/zephyr-7b-beta.Q4_K_M.gguf", "id": "zephyr-beta-7b", "object": "model", - "name": "Zephyr Beta 7B", + "name": "Zephyr Beta 7B Q4", "version": "1.0", - "description": "The Zephyr-7B-β model marks the second iteration in the Zephyr series, designed to function as an effective assistant. It has been fine-tuned from the mistralai/Mistral-7B-v0.1 base model, utilizing a combination of public and synthetic datasets with the application of Direct Preference Optimization.", + "description": "The Zephyr-7B-β model is trained by HuggingFace, designed to function as a practical assistant. It has been fine-tuned from the mistralai/Mistral-7B-v0.1 base model, utilizing a combination of public and synthetic datasets with the application of Direct Preference Optimization.", "format": "gguf", "settings": { "ctx_len": 2048, From f4dbec7e36527f4f022c55fbc1d426a4039d8851 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 16:31:47 +0700 Subject: [PATCH 025/179] update deepseek coder --- models/deepseek-coder-1.3b/model.json | 2 +- models/deepseek-coder-34b/model.json | 24 ++++++++++++++++++++++++ models/wizardcoder-34b/model.json | 24 ------------------------ 3 files changed, 25 insertions(+), 25 deletions(-) create mode 100644 models/deepseek-coder-34b/model.json delete mode 100644 models/wizardcoder-34b/model.json diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index 9d22f9558..2bb9e7fee 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -4,7 +4,7 @@ "object": "model", "name": "Deepseek Coder 1.3B Q4", "version": "1.0", - "description": "", + "description": "Deepseek Coder trained on 2T tokens (87% code, 13% English/Chinese), excelling in project-level code completion with advanced capabilities across multiple programming languages.", "format": "gguf", "settings": { "ctx_len": 2048, diff --git a/models/deepseek-coder-34b/model.json b/models/deepseek-coder-34b/model.json new file mode 100644 index 000000000..e175396f5 --- /dev/null +++ b/models/deepseek-coder-34b/model.json @@ -0,0 +1,24 @@ +{ + "source_url": "https://huggingface.co/TheBloke/deepseek-coder-33B-instruct-GGUF/resolve/main/deepseek-coder-33b-instruct.Q5_K_M.gguf", + "id": "deepseek-coder-34b", + "object": "model", + "name": "Deepseek Coder 33B Q5", + "version": "1.0", + "description": "Deepseek Coder trained on 2T tokens (87% code, 13% English/Chinese), excelling in project-level code completion with advanced capabilities across multiple programming languages.", + "format": "gguf", + "settings": { + "ctx_len": 2048, + "system_prompt": "", + "user_prompt": "### Instruction:\n", + "ai_prompt": "### Response:\n" + }, + "parameters": { + "max_tokens": 2048 + }, + "metadata": { + "author": "Deepseek, The Bloke", + "tags": ["Community Recommended", "Code", "Big Context Length"], + "size": 26040000000 + } + } + \ No newline at end of file diff --git a/models/wizardcoder-34b/model.json b/models/wizardcoder-34b/model.json deleted file mode 100644 index ca4a3da8e..000000000 --- a/models/wizardcoder-34b/model.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "source_url": "https://huggingface.co/TheBloke/WizardCoder-Python-34B-V1.0-GGUF/resolve/main/wizardcoder-python-34b-v1.0.Q5_K_M.gguf", - "id": "wizardcoder-34b", - "object": "model", - "name": "Wizard Coder Python 34B Q5", - "version": "1.0", - "description": "WizardCoder-Python-34B is a Python coding model major models like ChatGPT-3.5. This model based on the Llama2 architecture, demonstrate high proficiency in specific domains like coding and mathematics.", - "format": "gguf", - "settings": { - "ctx_len": 2048, - "system_prompt": "", - "user_prompt": "### Instruction:\n", - "ai_prompt": "### Response:\n" - }, - "parameters": { - "max_tokens": 2048 - }, - "metadata": { - "author": "WizardLM, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length"], - "size": 24320000000 - } - } - \ No newline at end of file From 69af5a034ad0026055259cd93b970107635a1a00 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 16:38:13 +0700 Subject: [PATCH 026/179] update tags --- models/capybara-34b/model.json | 2 +- models/deepseek-coder-1.3b/model.json | 2 +- models/deepseek-coder-34b/model.json | 2 +- models/neural-chat-7b/model.json | 2 +- models/noromaid-20b/model.json | 2 +- models/openhermes-neural-7b/model.json | 2 +- models/openorca-13b/model.json | 2 +- models/phind-34b/model.json | 2 +- models/rocket-3b/model.json | 2 +- models/starling-7b/model.json | 2 +- models/tiefighter-13b/model.json | 2 +- models/tinyllama-1.1b/model.json | 2 +- models/wizardcoder-13b/model.json | 2 +- models/yi-34b/model.json | 2 +- models/zephyr-beta-7b/model.json | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index e5834a66d..5f0bd7948 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "NousResearch, The Bloke", - "tags": ["Community Recommended", "General Use", "Big Context Length"], + "tags": ["Community Recommended", "Full Capabilities", "Finetuned"], "size": 24320000000 } } diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index 2bb9e7fee..47057626b 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Deepseek, The Bloke", - "tags": ["Community Recommended", "Code", "Small size"], + "tags": ["Foundational Model", "Code", "Small size"], "size": 870000000 } } diff --git a/models/deepseek-coder-34b/model.json b/models/deepseek-coder-34b/model.json index e175396f5..098cf87fd 100644 --- a/models/deepseek-coder-34b/model.json +++ b/models/deepseek-coder-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Deepseek, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length"], + "tags": ["Foundational Model", "Community Recommended", "Code", "Big Context Length"], "size": 26040000000 } } diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index 234a2ff80..5357d8822 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["Community Recommended", "General Use", "Role-playing", "Big Context Length"], + "tags": ["Community Recommended", "Full Capabilities", "Big Context Length", "Finetuned"], "size": 4370000000 } } diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index da69cba3e..e8e3a9128 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "NeverSleep, The Bloke", - "tags": ["Community Recommended", "Role-playing"], + "tags": ["Community Recommended", "Role-playing", "Finetuned"], "size": 12040000000 } } diff --git a/models/openhermes-neural-7b/model.json b/models/openhermes-neural-7b/model.json index a7366f85b..2e9452067 100644 --- a/models/openhermes-neural-7b/model.json +++ b/models/openhermes-neural-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["Community Recommended", "General Use", "Code", "Merged"], + "tags": ["Community Recommended", "Full Capabilities", "Merged"], "size": 4370000000 } } diff --git a/models/openorca-13b/model.json b/models/openorca-13b/model.json index edfef0807..709cb0d36 100644 --- a/models/openorca-13b/model.json +++ b/models/openorca-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Microsoft, The Bloke", - "tags": ["Community Recommended", "General Use"], + "tags": ["Community Recommended", "General", "Reasoning", "Finetuned"], "size": 9230000000 } } diff --git a/models/phind-34b/model.json b/models/phind-34b/model.json index 8de7a84d0..532ad70fe 100644 --- a/models/phind-34b/model.json +++ b/models/phind-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Phind, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length"], + "tags": ["Community Recommended", "Code", "Big Context Length", "Finetuned"], "size": 24320000000 } } diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index 7435bfafb..cd790d83c 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "pansophic, The Bloke", - "tags": ["Community Recommended", "General Use"], + "tags": ["Community Recommended", "General", "Small Size"], "size": 1710000000 } } \ No newline at end of file diff --git a/models/starling-7b/model.json b/models/starling-7b/model.json index a7ba963fe..5da638a29 100644 --- a/models/starling-7b/model.json +++ b/models/starling-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Berkeley-nest, The Bloke", - "tags": ["Community Recommended", "General", "Code"], + "tags": ["Community Recommended", "Full Capabilities","Finetuned"], "size": 4370000000 } } diff --git a/models/tiefighter-13b/model.json b/models/tiefighter-13b/model.json index 9940c9a70..e7b962e65 100644 --- a/models/tiefighter-13b/model.json +++ b/models/tiefighter-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "KoboldAI, The Bloke", - "tags": ["Community Recommended", "General Use", "Role-playing", "Writing"], + "tags": ["Community Recommended", "Role-playing", "Creative", "Finetuned"], "size": 9230000000 } } diff --git a/models/tinyllama-1.1b/model.json b/models/tinyllama-1.1b/model.json index 92793d7f6..93fd35fc6 100644 --- a/models/tinyllama-1.1b/model.json +++ b/models/tinyllama-1.1b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "TinyLlama", - "tags": ["Community Recommended", "General Use"], + "tags": ["Community Recommended", "General", "Small Size"], "size": 637000000 } } \ No newline at end of file diff --git a/models/wizardcoder-13b/model.json b/models/wizardcoder-13b/model.json index a7284aa7c..fc1f6f33c 100644 --- a/models/wizardcoder-13b/model.json +++ b/models/wizardcoder-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "WizardLM, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length"], + "tags": ["Community Recommended", "Code", "Big Context Length", "Finetuned"], "size": 9230000000 } } diff --git a/models/yi-34b/model.json b/models/yi-34b/model.json index bc6e8e9e3..7e574daf0 100644 --- a/models/yi-34b/model.json +++ b/models/yi-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "01-ai, The Bloke", - "tags": ["Foundational Model", "General Use", "Role-playing", "Writing"], + "tags": ["Foundational Model", "Full Capabilities"], "size": 24320000000 } } diff --git a/models/zephyr-beta-7b/model.json b/models/zephyr-beta-7b/model.json index e1a175432..8b53ab07e 100644 --- a/models/zephyr-beta-7b/model.json +++ b/models/zephyr-beta-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "HuggingFaceH4, The Bloke", - "tags": ["Community Recommended", "General Use", "Big Context Length"], + "tags": ["Community Recommended", "Full Capabilities", "Finetuned"], "size": 4370000000 } } From 3f5317bb53de9e2c9599c3a0f2f514b85ab92660 Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 16:44:43 +0700 Subject: [PATCH 027/179] typo --- models/lzlv-70b/model.json | 2 +- models/rocket-3b/model.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index e4247eb88..0b1577131 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -16,7 +16,7 @@ "max_tokens": 2048 }, "metadata": { - "author": "lizpreciatior, The Bloke", + "author": "Lizpreciatior, The Bloke", "tags": ["Community Recommended", "General Use", "Role-playing"], "size": 48750000000 } diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index cd790d83c..83a61584a 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -16,7 +16,7 @@ "max_tokens": 2048 }, "metadata": { - "author": "pansophic, The Bloke", + "author": "Pansophic, The Bloke", "tags": ["Community Recommended", "General", "Small Size"], "size": 1710000000 } From ff69e4fe51abe2dcc29d9b485c09f238cb1b73ee Mon Sep 17 00:00:00 2001 From: hahuyhoang411 Date: Mon, 4 Dec 2023 17:33:26 +0700 Subject: [PATCH 028/179] update models --- models/capybara-34b/model.json | 2 +- models/deepseek-coder-1.3b/model.json | 2 +- models/deepseek-coder-34b/model.json | 2 +- models/llama2-chat-70b-q4/model.json | 2 +- models/llama2-chat-7b-q4/model.json | 2 +- models/llama2-chat-7b-q5/model.json | 2 +- models/lzlv-70b/model.json | 2 +- models/mistral-ins-7b-q4/model.json | 2 +- models/mistral-ins-7b-q5/model.json | 2 +- models/neural-chat-7b/model.json | 2 +- models/noromaid-20b/model.json | 2 +- models/openhermes-neural-7b/model.json | 2 +- models/openorca-13b/model.json | 2 +- models/phind-34b/model.json | 2 +- models/rocket-3b/model.json | 2 +- models/starling-7b/model.json | 2 +- models/tiefighter-13b/model.json | 2 +- models/tinyllama-1.1b/model.json | 2 +- models/wizardcoder-13b/model.json | 2 +- models/yi-34b/model.json | 2 +- models/zephyr-beta-7b/model.json | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/models/capybara-34b/model.json b/models/capybara-34b/model.json index 5f0bd7948..14cdf1d04 100644 --- a/models/capybara-34b/model.json +++ b/models/capybara-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "NousResearch, The Bloke", - "tags": ["Community Recommended", "Full Capabilities", "Finetuned"], + "tags": ["Recommended", "Medium", "Finetuned"], "size": 24320000000 } } diff --git a/models/deepseek-coder-1.3b/model.json b/models/deepseek-coder-1.3b/model.json index 47057626b..ec2e2815f 100644 --- a/models/deepseek-coder-1.3b/model.json +++ b/models/deepseek-coder-1.3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Deepseek, The Bloke", - "tags": ["Foundational Model", "Code", "Small size"], + "tags": ["Tiny", "Foundational Model"], "size": 870000000 } } diff --git a/models/deepseek-coder-34b/model.json b/models/deepseek-coder-34b/model.json index 098cf87fd..1936df1b9 100644 --- a/models/deepseek-coder-34b/model.json +++ b/models/deepseek-coder-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Deepseek, The Bloke", - "tags": ["Foundational Model", "Community Recommended", "Code", "Big Context Length"], + "tags": ["Recommended", "Medium", "Foundational Model"], "size": 26040000000 } } diff --git a/models/llama2-chat-70b-q4/model.json b/models/llama2-chat-70b-q4/model.json index 4ab3ac555..6d089f9fa 100644 --- a/models/llama2-chat-70b-q4/model.json +++ b/models/llama2-chat-70b-q4/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "MetaAI, The Bloke", - "tags": ["Foundational Model", "General", "Code"], + "tags": ["Large", "Foundational Model"], "size": 43920000000 } } diff --git a/models/llama2-chat-7b-q4/model.json b/models/llama2-chat-7b-q4/model.json index 7b52e1767..3ba054018 100644 --- a/models/llama2-chat-7b-q4/model.json +++ b/models/llama2-chat-7b-q4/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "MetaAI, The Bloke", - "tags": ["Foundational Model", "General", "Code"], + "tags": ["Small", "Foundational Model"], "size": 4080000000 } } diff --git a/models/llama2-chat-7b-q5/model.json b/models/llama2-chat-7b-q5/model.json index b19213e46..2889868d5 100644 --- a/models/llama2-chat-7b-q5/model.json +++ b/models/llama2-chat-7b-q5/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "MetaAI, The Bloke", - "tags": ["Foundational Model", "General", "Code"], + "tags": ["Small", "Foundational Model"], "size": 4780000000 } } diff --git a/models/lzlv-70b/model.json b/models/lzlv-70b/model.json index 0b1577131..d26ba7d29 100644 --- a/models/lzlv-70b/model.json +++ b/models/lzlv-70b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Lizpreciatior, The Bloke", - "tags": ["Community Recommended", "General Use", "Role-playing"], + "tags": ["Recommended", "Large", "Finetuned"], "size": 48750000000 } } diff --git a/models/mistral-ins-7b-q4/model.json b/models/mistral-ins-7b-q4/model.json index dd3360700..fe1bb0a5b 100644 --- a/models/mistral-ins-7b-q4/model.json +++ b/models/mistral-ins-7b-q4/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "MistralAI, The Bloke", - "tags": ["Foundational Model", "General", "Code"], + "tags": ["Small", "Foundational Model"], "size": 4370000000 } } diff --git a/models/mistral-ins-7b-q5/model.json b/models/mistral-ins-7b-q5/model.json index 5b86ab538..13eb6bde9 100644 --- a/models/mistral-ins-7b-q5/model.json +++ b/models/mistral-ins-7b-q5/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "MistralAI, The Bloke", - "tags": ["Foundational Model", "General", "Code"], + "tags": ["Small", "Foundational Model"], "size": 5130000000 } } diff --git a/models/neural-chat-7b/model.json b/models/neural-chat-7b/model.json index 5357d8822..cd0b3b3d0 100644 --- a/models/neural-chat-7b/model.json +++ b/models/neural-chat-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["Community Recommended", "Full Capabilities", "Big Context Length", "Finetuned"], + "tags": ["Recommended", "Small", "Finetuned"], "size": 4370000000 } } diff --git a/models/noromaid-20b/model.json b/models/noromaid-20b/model.json index e8e3a9128..f6994d1ce 100644 --- a/models/noromaid-20b/model.json +++ b/models/noromaid-20b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "NeverSleep, The Bloke", - "tags": ["Community Recommended", "Role-playing", "Finetuned"], + "tags": ["Medium", "Finetuned"], "size": 12040000000 } } diff --git a/models/openhermes-neural-7b/model.json b/models/openhermes-neural-7b/model.json index 2e9452067..536345fd1 100644 --- a/models/openhermes-neural-7b/model.json +++ b/models/openhermes-neural-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Intel, The Bloke", - "tags": ["Community Recommended", "Full Capabilities", "Merged"], + "tags": ["Recommended", "Small", "Merged"], "size": 4370000000 } } diff --git a/models/openorca-13b/model.json b/models/openorca-13b/model.json index 709cb0d36..0c0c9fff7 100644 --- a/models/openorca-13b/model.json +++ b/models/openorca-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Microsoft, The Bloke", - "tags": ["Community Recommended", "General", "Reasoning", "Finetuned"], + "tags": ["Medium", "Finetuned"], "size": 9230000000 } } diff --git a/models/phind-34b/model.json b/models/phind-34b/model.json index 532ad70fe..c8e2cb919 100644 --- a/models/phind-34b/model.json +++ b/models/phind-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Phind, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length", "Finetuned"], + "tags": ["Recommended", "Medium", "Finetuned"], "size": 24320000000 } } diff --git a/models/rocket-3b/model.json b/models/rocket-3b/model.json index 83a61584a..c40ee6258 100644 --- a/models/rocket-3b/model.json +++ b/models/rocket-3b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Pansophic, The Bloke", - "tags": ["Community Recommended", "General", "Small Size"], + "tags": ["Tiny", "Finetuned"], "size": 1710000000 } } \ No newline at end of file diff --git a/models/starling-7b/model.json b/models/starling-7b/model.json index 5da638a29..1bc2aac61 100644 --- a/models/starling-7b/model.json +++ b/models/starling-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "Berkeley-nest, The Bloke", - "tags": ["Community Recommended", "Full Capabilities","Finetuned"], + "tags": ["Recommended", "Small","Finetuned"], "size": 4370000000 } } diff --git a/models/tiefighter-13b/model.json b/models/tiefighter-13b/model.json index e7b962e65..a28f88d85 100644 --- a/models/tiefighter-13b/model.json +++ b/models/tiefighter-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "KoboldAI, The Bloke", - "tags": ["Community Recommended", "Role-playing", "Creative", "Finetuned"], + "tags": ["Medium", "Finetuned"], "size": 9230000000 } } diff --git a/models/tinyllama-1.1b/model.json b/models/tinyllama-1.1b/model.json index 93fd35fc6..ace0ca6a0 100644 --- a/models/tinyllama-1.1b/model.json +++ b/models/tinyllama-1.1b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "TinyLlama", - "tags": ["Community Recommended", "General", "Small Size"], + "tags": ["Tiny", "Foundation Model"], "size": 637000000 } } \ No newline at end of file diff --git a/models/wizardcoder-13b/model.json b/models/wizardcoder-13b/model.json index fc1f6f33c..16f8946be 100644 --- a/models/wizardcoder-13b/model.json +++ b/models/wizardcoder-13b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "WizardLM, The Bloke", - "tags": ["Community Recommended", "Code", "Big Context Length", "Finetuned"], + "tags": ["Recommended", "Medium", "Finetuned"], "size": 9230000000 } } diff --git a/models/yi-34b/model.json b/models/yi-34b/model.json index 7e574daf0..c2d2fd327 100644 --- a/models/yi-34b/model.json +++ b/models/yi-34b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "01-ai, The Bloke", - "tags": ["Foundational Model", "Full Capabilities"], + "tags": ["Medium", "Foundational Model"], "size": 24320000000 } } diff --git a/models/zephyr-beta-7b/model.json b/models/zephyr-beta-7b/model.json index 8b53ab07e..c6b4bcdc7 100644 --- a/models/zephyr-beta-7b/model.json +++ b/models/zephyr-beta-7b/model.json @@ -17,7 +17,7 @@ }, "metadata": { "author": "HuggingFaceH4, The Bloke", - "tags": ["Community Recommended", "Full Capabilities", "Finetuned"], + "tags": ["Recommended", "Small", "Finetuned"], "size": 4370000000 } } From dacb00ed3757b7b4bdb1d0527d7dee2969b4811d Mon Sep 17 00:00:00 2001 From: NamH Date: Mon, 4 Dec 2023 17:40:47 +0700 Subject: [PATCH 029/179] fix: not update active model when using resend button (#834) Signed-off-by: James Co-authored-by: James --- web/hooks/useSendChatMessage.ts | 42 ++++++++++++++++++ web/screens/Chat/MessageToolbar/index.tsx | 52 +++++++++-------------- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 9cf61969d..8b9a1bada 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -48,6 +48,47 @@ export default function useSendChatMessage() { const { startModel } = useActiveModel() const [queuedMessage, setQueuedMessage] = useState(false) + const resendChatMessage = async () => { + if (!activeThread) { + console.error('No active thread') + return + } + + updateThreadWaiting(activeThread.id, true) + + const messages: ChatCompletionMessage[] = [ + activeThread.assistants[0]?.instructions, + ] + .map((instructions) => { + const systemMessage: ChatCompletionMessage = { + role: ChatCompletionRole.System, + content: instructions, + } + return systemMessage + }) + .concat( + currentMessages.map((msg) => ({ + role: msg.role, + content: msg.content[0]?.text.value ?? '', + })) + ) + + const messageRequest: MessageRequest = { + id: ulid(), + messages: messages, + threadId: activeThread.id, + } + + const modelId = selectedModel?.id ?? activeThread.assistants[0].model.id + + if (activeModel?.id !== modelId) { + setQueuedMessage(true) + await startModel(modelId) + setQueuedMessage(false) + } + events.emit(EventName.OnMessageSent, messageRequest) + } + const sendChatMessage = async () => { if (!currentPrompt || currentPrompt.trim().length === 0) { return @@ -162,6 +203,7 @@ export default function useSendChatMessage() { return { sendChatMessage, + resendChatMessage, queuedMessage, } } diff --git a/web/screens/Chat/MessageToolbar/index.tsx b/web/screens/Chat/MessageToolbar/index.tsx index 5380c7e29..a0929336c 100644 --- a/web/screens/Chat/MessageToolbar/index.tsx +++ b/web/screens/Chat/MessageToolbar/index.tsx @@ -1,7 +1,5 @@ import { - ChatCompletionMessage, EventName, - MessageRequest, MessageStatus, ExtensionType, ThreadMessage, @@ -21,17 +19,15 @@ import { getCurrentChatMessagesAtom, } from '@/helpers/atoms/ChatMessage.atom' import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom' +import useSendChatMessage from '@/hooks/useSendChatMessage' const MessageToolbar = ({ message }: { message: ThreadMessage }) => { const deleteMessage = useSetAtom(deleteMessageAtom) const thread = useAtomValue(activeThreadAtom) const messages = useAtomValue(getCurrentChatMessagesAtom) - // const threadStateAtom = useMemo( - // () => atom((get) => get(threadStatesAtom)[thread?.id ?? '']), - // [thread?.id] - // ) - // const threadState = useAtomValue(threadStateAtom) - const stopInference = async () => { + const { resendChatMessage } = useSendChatMessage() + + const onStopInferenceClick = async () => { await extensionManager .get(ExtensionType.Inference) ?.stopInference() @@ -43,13 +39,25 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => { }, 300) } + const onDeleteClick = async () => { + deleteMessage(message.id ?? '') + if (thread) { + await extensionManager + .get(ExtensionType.Conversational) + ?.writeMessages( + thread.id, + messages.filter((msg) => msg.id !== message.id) + ) + } + } + return (
{message.status === MessageStatus.Pending && (
stopInference()} + onClick={onStopInferenceClick} >
@@ -58,20 +66,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => { message.id === messages[messages.length - 1]?.id && (
{ - const messageRequest: MessageRequest = { - id: message.id ?? '', - messages: messages.slice(0, -1).map((e) => { - const msg: ChatCompletionMessage = { - role: e.role, - content: e.content[0].text.value, - } - return msg - }), - threadId: message.thread_id ?? '', - } - events.emit(EventName.OnMessageSent, messageRequest) - }} + onClick={resendChatMessage} >
@@ -89,16 +84,7 @@ const MessageToolbar = ({ message }: { message: ThreadMessage }) => {
{ - deleteMessage(message.id ?? '') - if (thread) - await extensionManager - .get(ExtensionType.Conversational) - ?.writeMessages( - thread.id, - messages.filter((msg) => msg.id !== message.id) - ) - }} + onClick={onDeleteClick} >
From f99bf0f00855d22d90b1d50e338613a93509dc38 Mon Sep 17 00:00:00 2001 From: tikikun Date: Mon, 4 Dec 2023 17:53:18 +0700 Subject: [PATCH 030/179] add json schema for engine and model parameters --- .../specs/engineering/inference-parameters.md | 171 ++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 172 insertions(+) create mode 100644 docs/docs/specs/engineering/inference-parameters.md diff --git a/docs/docs/specs/engineering/inference-parameters.md b/docs/docs/specs/engineering/inference-parameters.md new file mode 100644 index 000000000..52eaa8a77 --- /dev/null +++ b/docs/docs/specs/engineering/inference-parameters.md @@ -0,0 +1,171 @@ +--- +title: "Inference Parameters" +slug: /specs/inference-parameters +description: Exhaustive list of json-schema for engine and models +--- + +# model_parameters + +```js + +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["messages"], + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object" + } + }, + "model": { + "type": "string" + }, + "frequency_penalty": { + "type": ["number", "null"], + "minimum": -2.0, + "maximum": 2.0, + "default": 0 + }, + "logit_bias": { + "type": ["object", "null"], + "additionalProperties": { + "type": "number", + "minimum": -100, + "maximum": 100 + }, + "default": null + }, + "max_tokens": { + "type": ["integer", "null"] + }, + "n": { + "type": ["integer", "null"], + "default": 1 + }, + "presence_penalty": { + "type": ["number", "null"], + "minimum": -2.0, + "maximum": 2.0, + "default": 0 + }, + "response_format": { + "type": ["object", "null"], + "properties": { + "type": { + "type": "string" + } + } + }, + "seed": { + "type": ["integer", "null"] + }, + "stop": { + "type": ["string", "array", "null"], + "items": { + "type": "string" + } + }, + "stream": { + "type": ["boolean", "null"], + "default": false + }, + "temperature": { + "type": ["number", "null"], + "minimum": 0, + "maximum": 2, + "default": 1 + }, + "top_p": { + "type": ["number", "null"], + "minimum": 0, + "maximum": 1, + "default": 1 + }, + "tools": { + "type": ["array", "null"], + "items": { + "type": "object" + } + }, + "tool_choice": { + "type": ["string", "object", "null"] + }, + "user": { + "type": ["string", "null"] + }, + "function_call": { + "type": ["string", "object", "null"], + "deprecated": true + }, + "functions": { + "type": ["array", "null"], + "items": { + "type": "object" + }, + "deprecated": true + } + } +} + +``` + +# nitro engine_parameters + +```js +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "pre_prompt": { + "type": "string", + "description": "The prompt to use for internal configuration." + }, + "system_prompt": { + "type": "string", + "description": "The prefix for system prompt." + }, + "user_prompt": { + "type": "string", + "description": "The prefix for user prompt." + }, + "ai_prompt": { + "type": "string", + "description": "The prefix for assistant prompt." + }, + "ngl": { + "type": "integer", + "default": 100, + "minimum": 0, + "maximum": 100, + "description": "The number of layers to load onto the GPU for acceleration." + }, + "ctx_len": { + "type": "integer", + "default": 2048, + "minimum": 128, + "maximum": 4096, + "description": "The context length for model operations varies; the maximum depends on the specific model used." + }, + "n_parallel": { + "type": "integer", + "default": 1, + "description": "The number of parallel operations. Only set when enable continuous batching." + }, + "cont_batching": { + "type": "boolean", + "default": false, + "description": "Whether to use continuous batching." + }, + "cpu_threads": { + "type": "integer", + "description": "The number of threads for CPU-based inference." + }, + "embedding": { + "type": "boolean", + "description": "Whether to enable embedding." + } + } +} +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index 384f47e9d..e1d5da5c6 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -82,6 +82,7 @@ const sidebars = { "specs/engineering/chats", "specs/engineering/models", "specs/engineering/engine", + "specs/engineering/inference-parameters", "specs/engineering/threads", "specs/engineering/messages", "specs/engineering/assistants", From f6f5db01f8a99c7e9897027a2567cb58fd7acf6d Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Mon, 4 Dec 2023 12:30:09 +0700 Subject: [PATCH 031/179] Resolve comments + update licence --- core/src/fs.ts | 2 -- server/.gitignore | 1 + server/package.json | 2 +- server/v1/index.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) create mode 100644 server/.gitignore diff --git a/core/src/fs.ts b/core/src/fs.ts index b428a7841..d12b473bf 100644 --- a/core/src/fs.ts +++ b/core/src/fs.ts @@ -1,5 +1,3 @@ -const fetchRetry = require("fetch-retry")(global.fetch); - /** * Writes data to a file at the specified path. * @param {string} path - The path to the file. diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 000000000..6320cd248 --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +data \ No newline at end of file diff --git a/server/package.json b/server/package.json index 405f2bf5b..76262ba51 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,7 @@ "version": "0.1.3", "main": "./build/main.js", "author": "Jan ", - "license": "MIT", + "license": "AGPL-3.0", "homepage": "https://github.com/janhq/jan/tree/main/electron", "description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.", "build": { diff --git a/server/v1/index.ts b/server/v1/index.ts index 8a153cbaf..89d73200b 100644 --- a/server/v1/index.ts +++ b/server/v1/index.ts @@ -9,7 +9,7 @@ const router: FastifyPluginAsync = async (app: FastifyInstance, opts) => { app.register( assistantsAPI, { - prefix: "/assisstants" + prefix: "/assistants" } ) From 12e765109e903357fc13a2872714ed67a1193e99 Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Mon, 4 Dec 2023 21:37:02 +0700 Subject: [PATCH 032/179] resolve comments --- server/main.ts | 12 +++++---- server/package.json | 61 +++------------------------------------------ 2 files changed, 11 insertions(+), 62 deletions(-) diff --git a/server/main.ts b/server/main.ts index 5466a27d8..758aa2217 100644 --- a/server/main.ts +++ b/server/main.ts @@ -1,17 +1,19 @@ import fastify from 'fastify' - +import dotenv from 'dotenv' import v1API from './v1' -const JAN_API_PORT = 1337; const server = fastify() -const USER_ROOT_DIR = '.data' +dotenv.config() server.register(v1API, {prefix: "/api/v1"}) +const JAN_API_PORT = Number.parseInt(process.env.JAN_API_PORT || '1337') +const JAN_API_HOST = process.env.JAN_API_HOST || "0.0.0.0" + server.listen({ port: JAN_API_PORT, - host: "0.0.0.0" -}).then(()=>{ + host: JAN_API_HOST +}).then(() => { console.log(`JAN API listening at: http://0.0.0.0:${JAN_API_PORT}`); }) diff --git a/server/package.json b/server/package.json index 76262ba51..1fd06a482 100644 --- a/server/package.json +++ b/server/package.json @@ -4,76 +4,23 @@ "main": "./build/main.js", "author": "Jan ", "license": "AGPL-3.0", - "homepage": "https://github.com/janhq/jan/tree/main/electron", + "homepage": "https://jan.ai", "description": "Use offline LLMs with your own data. Run open source models like Llama2 or Falcon on your internal computers/servers.", - "build": { - "appId": "jan.ai.app", - "productName": "Jan", - "files": [ - "renderer/**/*", - "build/*.{js,map}", - "build/**/*.{js,map}", - "core/pre-install", - "core/plugin-manager/facade" - ], - "asarUnpack": [ - "core/pre-install" - ], - "publish": [ - { - "provider": "github", - "owner": "janhq", - "repo": "jan" - } - ], - "extends": null, - "mac": { - "type": "distribution", - "entitlements": "./entitlements.mac.plist", - "entitlementsInherit": "./entitlements.mac.plist", - "notarize": { - "teamId": "YT49P7GXG4" - }, - "icon": "icons/icon.png" - }, - "linux": { - "target": [ - "deb" - ], - "category": "Utility", - "icon": "icons/" - }, - "win": { - "icon": "icons/icon.png" - }, - "artifactName": "jan-${os}-${arch}-${version}.${ext}" - }, + "build": "", "scripts": { "lint": "eslint . --ext \".js,.jsx,.ts,.tsx\"", "test:e2e": "playwright test --workers=1", "dev": "nodemon .", - "build": "tsc", - "build:test": "", - "build:publish": "" + "build": "tsc" }, "dependencies": { - "@npmcli/arborist": "^7.1.0", - "@types/request": "^2.48.12", - "@uiball/loaders": "^1.3.0", - "express": "^4.18.2", - "pacote": "^17.0.4", - "request": "^2.88.2", - "request-progress": "^3.0.0", - "use-debounce": "^9.0.4" }, "devDependencies": { - "@playwright/test": "^1.38.1", "@types/body-parser": "^1.19.5", - "@types/express": "^4.17.21", "@types/npmcli__arborist": "^5.6.4", - "@types/pacote": "^11.1.7", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", + "dotenv": "^16.3.1", "eslint-plugin-react": "^7.33.2", "fastify": "^4.24.3", "nodemon": "^3.0.1", From dbcd3573c4eab03fafb66c866b2ac6fec0366d88 Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Mon, 4 Dec 2023 21:44:21 +0700 Subject: [PATCH 033/179] Remove old merge mistake - auto merge adding lines to Makefile --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index 76dff5614..13618cf6a 100644 --- a/Makefile +++ b/Makefile @@ -15,10 +15,6 @@ endif # Installs yarn dependencies and builds core and extensions install-and-build: build-uikit ifeq ($(OS),Windows_NT) - powershell -Command "yarn config set network-timeout 300000; \ - $$env:NITRO_VERSION = Get-Content .\\plugins\\inference-plugin\\nitro\\version.txt; \ - Write-Output \"Nitro version: $$env:NITRO_VERSION\"; yarn build:core; yarn install; yarn build:plugins" -else yarn config set network-timeout 300000 endif yarn build:core From d0e657161e2b799dc566473741a2c854aca2a29d Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Mon, 4 Dec 2023 22:04:41 +0700 Subject: [PATCH 034/179] fix console print --- server/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/main.ts b/server/main.ts index 758aa2217..707c3fb4b 100644 --- a/server/main.ts +++ b/server/main.ts @@ -14,6 +14,6 @@ server.listen({ port: JAN_API_PORT, host: JAN_API_HOST }).then(() => { - console.log(`JAN API listening at: http://0.0.0.0:${JAN_API_PORT}`); + console.log(`JAN API listening at: http://${JAN_API_HOST}:${JAN_API_PORT}`); }) From f7e3dfcc11d3ece811420fd9260b6056f5bdbfa3 Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:30:09 +0700 Subject: [PATCH 035/179] Bump nitro version to 0.1.21 - nitro has windows codesign (#843) --- extensions/inference-extension/nitro/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/inference-extension/nitro/version.txt b/extensions/inference-extension/nitro/version.txt index 964f548b5..790629964 100644 --- a/extensions/inference-extension/nitro/version.txt +++ b/extensions/inference-extension/nitro/version.txt @@ -1 +1 @@ -0.1.20 \ No newline at end of file +0.1.21 From 5d5139ad202bbf1edf3faca16b2ea727f31e6317 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 4 Dec 2023 11:14:07 +0700 Subject: [PATCH 036/179] Added bannner and search ui hub screen --- uikit/src/button/index.tsx | 1 + uikit/src/button/styles.scss | 4 ++ web/public/images/hub-banner.png | Bin 0 -> 285174 bytes .../ExploreModels/ExploreModelItem/index.tsx | 2 +- .../ExploreModelItemHeader/index.tsx | 9 ++- web/screens/ExploreModels/index.tsx | 59 +++++++++++++++++- 6 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 web/public/images/hub-banner.png diff --git a/uikit/src/button/index.tsx b/uikit/src/button/index.tsx index 943e2bc34..55be33f98 100644 --- a/uikit/src/button/index.tsx +++ b/uikit/src/button/index.tsx @@ -14,6 +14,7 @@ const buttonVariants = cva('btn', { outline: 'btn-outline', secondary: 'btn-secondary', ghost: 'btn-ghost', + success: 'btn-success', }, size: { sm: 'btn-sm', diff --git a/uikit/src/button/styles.scss b/uikit/src/button/styles.scss index 69d8e522e..b103987c9 100644 --- a/uikit/src/button/styles.scss +++ b/uikit/src/button/styles.scss @@ -19,6 +19,10 @@ @apply bg-secondary text-secondary-foreground hover:bg-secondary/80; } + &-success { + @apply bg-green-500 text-white hover:bg-green-500/80; + } + &-ghost { @apply hover:bg-primary hover:text-primary-foreground; } diff --git a/web/public/images/hub-banner.png b/web/public/images/hub-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..2734c20f5a110b669edbf946be5f925f6372a479 GIT binary patch literal 285174 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%0D% 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 literal 0 HcmV?d00001 diff --git a/web/screens/ExploreModels/ExploreModelItem/index.tsx b/web/screens/ExploreModels/ExploreModelItem/index.tsx index b2c12f86d..5b0c1e408 100644 --- a/web/screens/ExploreModels/ExploreModelItem/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItem/index.tsx @@ -15,7 +15,7 @@ const ExploreModelItem = forwardRef(({ model }, ref) => { return (
diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index eb2cdee31..6345d5f1e 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -24,6 +24,7 @@ type Props = { } const ExploreModelItemHeader: React.FC = ({ model }) => { + console.log(model) const { downloadModel } = useDownloadModel() const { downloadedModels } = useGetDownloadedModels() const { modelDownloadStateAtom, downloadStates } = useDownloadState() @@ -57,6 +58,8 @@ const ExploreModelItemHeader: React.FC = ({ model }) => { if (isDownloaded) { downloadButton = ( ) : ( - + )} diff --git a/web/screens/ExploreModels/ExploreModelItem/index.tsx b/web/screens/ExploreModels/ExploreModelItem/index.tsx index 5b0c1e408..1ca539a7d 100644 --- a/web/screens/ExploreModels/ExploreModelItem/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItem/index.tsx @@ -1,6 +1,6 @@ /* eslint-disable react/display-name */ -import { forwardRef } from 'react' +import { forwardRef, useState } from 'react' import { Model } from '@janhq/core' import { Badge } from '@janhq/uikit' @@ -12,58 +12,78 @@ type Props = { } const ExploreModelItem = forwardRef(({ model }, ref) => { + const [open, setOpen] = useState('') + + const handleToggle = () => { + if (open === model.id) { + setOpen('') + } else { + setOpen(model.id) + } + } + return (
- -
-
- About -

{model.description}

-
- -
-
- Author -

{model.metadata.author}

+ + {open === model.id && ( +
+
+
+ About +

+ {model.description || '-'} +

+
+
+
+ + Author + +

{model.metadata.author}

+
+
+ + Model ID + +

{model.id}

+
+
+ + Tags + +
+ {model.metadata.tags.map((tag, i) => ( + + {tag} + + ))} +
+
+
-
- Compatibility -
- {/* - {toGigabytes(model.metadata.maxRamRequired)} RAM required - */} +
+
+ + Format + +

{model.format}

+
+
+ + Compatibility + +

-

- -
-
- Version -
- v{model.version} -
-
-
- Tags -
- {model.metadata.tags.map((tag, i) => ( - - {tag} - - ))} -
-
-
-
+ )}
) }) diff --git a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx index 6345d5f1e..6c34a2074 100644 --- a/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx +++ b/web/screens/ExploreModels/ExploreModelItemHeader/index.tsx @@ -1,16 +1,20 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useCallback, useMemo, useState } from 'react' +import { useCallback, useMemo } from 'react' import { Model } from '@janhq/core' import { Badge, Button } from '@janhq/uikit' import { atom, useAtomValue } from 'jotai' +import { ChevronDownIcon } from 'lucide-react' + +import { twMerge } from 'tailwind-merge' + import ModalCancelDownload from '@/containers/ModalCancelDownload' import { MainViewState } from '@/constants/screens' -import { ModelPerformance, TagType } from '@/constants/tagType' +// import { ModelPerformance, TagType } from '@/constants/tagType' import useDownloadModel from '@/hooks/useDownloadModel' import { useDownloadState } from '@/hooks/useDownloadState' @@ -21,18 +25,20 @@ import { toGigabytes } from '@/utils/converter' type Props = { model: Model + onClick: () => void + open: string } -const ExploreModelItemHeader: React.FC = ({ model }) => { - console.log(model) +const ExploreModelItemHeader: React.FC = ({ model, onClick, open }) => { const { downloadModel } = useDownloadModel() const { downloadedModels } = useGetDownloadedModels() const { modelDownloadStateAtom, downloadStates } = useDownloadState() - const [title, setTitle] = useState('Recommended') + // const [title, setTitle] = useState('Recommended') + + // const [performanceTag, setPerformanceTag] = useState( + // ModelPerformance.PerformancePositive + // ) - const [performanceTag, setPerformanceTag] = useState( - ModelPerformance.PerformancePositive - ) const downloadAtom = useMemo( () => atom((get) => get(modelDownloadStateAtom)[model.id]), [model.id] @@ -48,18 +54,14 @@ const ExploreModelItemHeader: React.FC = ({ model }) => { const isDownloaded = downloadedModels.find((md) => md.id === model.id) != null let downloadButton = ( - + ) if (isDownloaded) { downloadButton = (
- +
From 3aa58cee62a2c7f2b6f53d4a4340091a0015264a Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 4 Dec 2023 19:18:18 +0700 Subject: [PATCH 038/179] Finished flow hub screen --- web/containers/DropdownListSidebar/index.tsx | 14 +++++++++++--- web/screens/Chat/index.tsx | 2 +- .../ExploreModels/ExploreModelItemHeader/index.tsx | 3 +++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/web/containers/DropdownListSidebar/index.tsx b/web/containers/DropdownListSidebar/index.tsx index 589847fdf..84bc6623b 100644 --- a/web/containers/DropdownListSidebar/index.tsx +++ b/web/containers/DropdownListSidebar/index.tsx @@ -19,6 +19,7 @@ import { twMerge } from 'tailwind-merge' import { MainViewState } from '@/constants/screens' +import { useActiveModel } from '@/hooks/useActiveModel' import { getDownloadedModels } from '@/hooks/useGetDownloadedModels' import { useMainViewState } from '@/hooks/useMainViewState' @@ -35,6 +36,7 @@ export default function DropdownListSidebar() { const activeThread = useAtomValue(activeThreadAtom) const [selected, setSelected] = useState() const { setMainViewState } = useMainViewState() + const { activeModel, stateModel } = useActiveModel() useEffect(() => { getDownloadedModels().then((downloadedModels) => { @@ -42,18 +44,24 @@ export default function DropdownListSidebar() { if (downloadedModels.length > 0) { setSelected( downloadedModels.filter( - (x) => x.id === activeThread?.assistants[0].model.id + (x) => + x.id === activeThread?.assistants[0].model.id || + x.id === activeModel?.id )[0] || downloadedModels[0] ) setSelectedModel( downloadedModels.filter( - (x) => x.id === activeThread?.assistants[0].model.id + (x) => + x.id === activeThread?.assistants[0].model.id || + x.id === activeModel?.id )[0] || downloadedModels[0] ) } }) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeThread]) + }, [activeThread, activeModel]) + + if (stateModel.loading) return null return ( Date: Tue, 5 Dec 2023 10:21:41 +0700 Subject: [PATCH 044/179] Fix broken position banner hub screen --- web/screens/ExploreModels/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/screens/ExploreModels/index.tsx b/web/screens/ExploreModels/index.tsx index de7569212..5e584458c 100644 --- a/web/screens/ExploreModels/index.tsx +++ b/web/screens/ExploreModels/index.tsx @@ -58,7 +58,11 @@ const ExploreModelsScreen = () => {
- Hub Banner + Hub Banner
Date: Tue, 5 Dec 2023 10:24:31 +0700 Subject: [PATCH 045/179] Temporary hide tabs element on hub screen --- web/screens/ExploreModels/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/web/screens/ExploreModels/index.tsx b/web/screens/ExploreModels/index.tsx index 5e584458c..fc52c973e 100644 --- a/web/screens/ExploreModels/index.tsx +++ b/web/screens/ExploreModels/index.tsx @@ -78,8 +78,9 @@ const ExploreModelsScreen = () => {
-
-
+
+ {/* Temporary hide tabs */} + {/*
{ -
+
*/} { - setSelected(downloadedModels.filter((x) => x.id === value)[0]) - setSelectedModel(downloadedModels.filter((x) => x.id === value)[0]) - }} + onValueChange={finishInit ? undefined : onValueSelected} > @@ -86,22 +96,20 @@ export default function DropdownListSidebar() {
) : ( - {downloadedModels.map((x, i) => { - return ( - -
- {x.name} - - {toGigabytes(x.metadata.size)} - -
-
- ) - })} + {downloadedModels.map((x, i) => ( + +
+ {x.name} + + {toGigabytes(x.metadata.size)} + +
+
+ ))}
)}
diff --git a/web/containers/Layout/TopBar/index.tsx b/web/containers/Layout/TopBar/index.tsx index 4d5a6f4af..257af7de2 100644 --- a/web/containers/Layout/TopBar/index.tsx +++ b/web/containers/Layout/TopBar/index.tsx @@ -12,7 +12,7 @@ import { useMainViewState } from '@/hooks/useMainViewState' import { showRightSideBarAtom } from '@/screens/Chat/Sidebar' -import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' const TopBar = () => { const activeThread = useAtomValue(activeThreadAtom) diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index 92fb3150a..2502340ba 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -25,7 +25,7 @@ import { import { updateThreadWaitingForResponseAtom, threadsAtom, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' export default function EventHandler({ children }: { children: ReactNode }) { const addNewMessage = useSetAtom(addNewMessageAtom) diff --git a/web/containers/Slider/index.tsx b/web/containers/Slider/index.tsx new file mode 100644 index 000000000..c8daa17fb --- /dev/null +++ b/web/containers/Slider/index.tsx @@ -0,0 +1,101 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useEffect, useState } from 'react' + +import { Slider, Input } from '@janhq/uikit' +import { useAtomValue } from 'jotai' + +import useUpdateModelParameters from '@/hooks/useUpdateModelParameters' + +import { + getActiveThreadIdAtom, + getActiveThreadModelRuntimeParamsAtom, +} from '@/helpers/atoms/Thread.atom' + +type Props = { + name: string + title: string + min: number + max: number + step: number + value: number + register: any +} + +const SliderRightPanel: React.FC = ({ + name, + title, + min, + max, + step, + value, + register, +}) => { + const [currentValue, setCurrentValue] = useState(value) + const { updateModelParameter } = useUpdateModelParameters() + const threadId = useAtomValue(getActiveThreadIdAtom) + const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) + + useEffect(() => { + setCurrentValue(value) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + useEffect(() => { + updateSetting() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [currentValue]) + + const updateValue = [name].reduce((accumulator, value) => { + return { ...accumulator, [value]: currentValue } + }, {}) + + const updateSetting = () => { + return updateModelParameter(String(threadId), { + ...activeModelParams, + ...updateValue, + }) + } + + return ( +
+

{title}

+
+
+ parseInt(v), + })} + value={[currentValue]} + onValueChange={async (e) => { + setCurrentValue(Number(e[0])) + await updateSetting() + }} + type="range" + min={min} + max={max} + step={step} + /> +
+

{min}

+

+ {max / 2} +

+

{max}

+
+
+ { + setCurrentValue(Number(e.target.value)) + await updateSetting() + }} + /> +
+
+ ) +} + +export default SliderRightPanel diff --git a/web/helpers/atoms/ChatMessage.atom.ts b/web/helpers/atoms/ChatMessage.atom.ts index d365304e4..33309e6fc 100644 --- a/web/helpers/atoms/ChatMessage.atom.ts +++ b/web/helpers/atoms/ChatMessage.atom.ts @@ -9,7 +9,7 @@ import { atom } from 'jotai' import { getActiveThreadIdAtom, updateThreadStateLastMessageAtom, -} from './Conversation.atom' +} from './Thread.atom' /** * Stores all chat messages for all threads @@ -76,15 +76,18 @@ export const addNewMessageAtom = atom( } ) -export const deleteConversationMessage = atom(null, (get, set, id: string) => { - const newData: Record = { - ...get(chatMessages), +export const deleteChatMessageAtom = atom( + null, + (get, set, threadId: string) => { + const newData: Record = { + ...get(chatMessages), + } + newData[threadId] = [] + set(chatMessages, newData) } - newData[id] = [] - set(chatMessages, newData) -}) +) -export const cleanConversationMessages = atom(null, (get, set, id: string) => { +export const cleanChatMessageAtom = atom(null, (get, set, id: string) => { const newData: Record = { ...get(chatMessages), } diff --git a/web/helpers/atoms/Conversation.atom.ts b/web/helpers/atoms/Thread.atom.ts similarity index 53% rename from web/helpers/atoms/Conversation.atom.ts rename to web/helpers/atoms/Thread.atom.ts index 21a89c26b..5ec7173b1 100644 --- a/web/helpers/atoms/Conversation.atom.ts +++ b/web/helpers/atoms/Thread.atom.ts @@ -1,8 +1,13 @@ -import { Thread, ThreadContent, ThreadState } from '@janhq/core' +import { + ModelRuntimeParams, + Thread, + ThreadContent, + ThreadState, +} from '@janhq/core' import { atom } from 'jotai' /** - * Stores the current active conversation id. + * Stores the current active thread id. */ const activeThreadIdAtom = atom(undefined) @@ -10,7 +15,7 @@ export const getActiveThreadIdAtom = atom((get) => get(activeThreadIdAtom)) export const setActiveThreadIdAtom = atom( null, - (_get, set, convoId: string | undefined) => set(activeThreadIdAtom, convoId) + (_get, set, threadId: string | undefined) => set(activeThreadIdAtom, threadId) ) export const waitingToSendMessage = atom(undefined) @@ -20,47 +25,48 @@ export const waitingToSendMessage = atom(undefined) */ export const threadStatesAtom = atom>({}) export const activeThreadStateAtom = atom((get) => { - const activeConvoId = get(activeThreadIdAtom) - if (!activeConvoId) { - console.debug('Active convo id is undefined') + const threadId = get(activeThreadIdAtom) + if (!threadId) { + console.debug('Active thread id is undefined') return undefined } - return get(threadStatesAtom)[activeConvoId] + return get(threadStatesAtom)[threadId] }) +export const deleteThreadStateAtom = atom( + null, + (get, set, threadId: string) => { + const currentState = { ...get(threadStatesAtom) } + delete currentState[threadId] + set(threadStatesAtom, currentState) + } +) + +export const updateThreadInitSuccessAtom = atom( + null, + (get, set, threadId: string) => { + const currentState = { ...get(threadStatesAtom) } + currentState[threadId] = { + ...currentState[threadId], + isFinishInit: true, + } + set(threadStatesAtom, currentState) + } +) + export const updateThreadWaitingForResponseAtom = atom( null, - (get, set, conversationId: string, waitingForResponse: boolean) => { + (get, set, threadId: string, waitingForResponse: boolean) => { const currentState = { ...get(threadStatesAtom) } - currentState[conversationId] = { - ...currentState[conversationId], + currentState[threadId] = { + ...currentState[threadId], waitingForResponse, error: undefined, } set(threadStatesAtom, currentState) } ) -export const updateConversationErrorAtom = atom( - null, - (get, set, conversationId: string, error?: Error) => { - const currentState = { ...get(threadStatesAtom) } - currentState[conversationId] = { - ...currentState[conversationId], - error, - } - set(threadStatesAtom, currentState) - } -) -export const updateConversationHasMoreAtom = atom( - null, - (get, set, conversationId: string, hasMore: boolean) => { - const currentState = { ...get(threadStatesAtom) } - currentState[conversationId] = { ...currentState[conversationId], hasMore } - set(threadStatesAtom, currentState) - } -) - export const updateThreadStateLastMessageAtom = atom( null, (get, set, threadId: string, lastContent?: ThreadContent[]) => { @@ -100,3 +106,42 @@ export const threadsAtom = atom([]) export const activeThreadAtom = atom((get) => get(threadsAtom).find((c) => c.id === get(getActiveThreadIdAtom)) ) + +/** + * Store model params at thread level settings + */ +export const threadModelRuntimeParamsAtom = atom< + Record +>({}) + +export const getActiveThreadModelRuntimeParamsAtom = atom< + ModelRuntimeParams | undefined +>((get) => { + const threadId = get(activeThreadIdAtom) + if (!threadId) { + console.debug('Active thread id is undefined') + return undefined + } + + return get(threadModelRuntimeParamsAtom)[threadId] +}) + +export const getThreadModelRuntimeParamsAtom = atom( + (get, threadId: string) => get(threadModelRuntimeParamsAtom)[threadId] +) + +export const setThreadModelRuntimeParamsAtom = atom( + null, + (get, set, threadId: string, params: ModelRuntimeParams) => { + const currentState = { ...get(threadModelRuntimeParamsAtom) } + currentState[threadId] = params + console.debug( + `Update model params for thread ${threadId}, ${JSON.stringify( + params, + null, + 2 + )}` + ) + set(threadModelRuntimeParamsAtom, currentState) + } +) diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 699b16279..15084278c 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -1,8 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { - EventName, - events, -} from '@janhq/core' +import { EventName, events } from '@janhq/core' import { Model, ModelSettingParams } from '@janhq/core' import { atom, useAtom } from 'jotai' diff --git a/web/hooks/useCreateNewThread.ts b/web/hooks/useCreateNewThread.ts index e2f2aa35d..ff0b4d049 100644 --- a/web/hooks/useCreateNewThread.ts +++ b/web/hooks/useCreateNewThread.ts @@ -6,9 +6,9 @@ import { ThreadAssistantInfo, ThreadState, } from '@janhq/core' -import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai' +import { atom, useAtomValue, useSetAtom } from 'jotai' -import { generateThreadId } from '@/utils/conversation' +import { generateThreadId } from '@/utils/thread' import { extensionManager } from '@/extension' import { @@ -16,7 +16,8 @@ import { setActiveThreadIdAtom, threadStatesAtom, updateThreadAtom, -} from '@/helpers/atoms/Conversation.atom' + setThreadModelRuntimeParamsAtom, +} from '@/helpers/atoms/Thread.atom' const createNewThreadAtom = atom(null, (get, set, newThread: Thread) => { // create thread state for this new thread @@ -25,6 +26,8 @@ const createNewThreadAtom = atom(null, (get, set, newThread: Thread) => { const threadState: ThreadState = { hasMore: false, waitingForResponse: false, + lastMessage: undefined, + isFinishInit: false, } currentState[newThread.id] = threadState set(threadStatesAtom, currentState) @@ -35,15 +38,26 @@ const createNewThreadAtom = atom(null, (get, set, newThread: Thread) => { }) export const useCreateNewThread = () => { + const threadStates = useAtomValue(threadStatesAtom) const createNewThread = useSetAtom(createNewThreadAtom) const setActiveThreadId = useSetAtom(setActiveThreadIdAtom) - const [threadStates, setThreadStates] = useAtom(threadStatesAtom) - const threads = useAtomValue(threadsAtom) const updateThread = useSetAtom(updateThreadAtom) + const setThreadModelRuntimeParams = useSetAtom( + setThreadModelRuntimeParamsAtom + ) const requestCreateNewThread = async (assistant: Assistant) => { - const unfinishedThreads = threads.filter((t) => t.isFinishInit === false) - if (unfinishedThreads.length > 0) { + // loop through threads state and filter if there's any thread that is not finish init + let hasUnfinishedInitThread = false + for (const key in threadStates) { + const isFinishInit = threadStates[key].isFinishInit ?? true + if (!isFinishInit) { + hasUnfinishedInitThread = true + break + } + } + + if (hasUnfinishedInitThread) { return } @@ -53,18 +67,10 @@ export const useCreateNewThread = () => { assistant_name: assistant.name, model: { id: '*', - settings: { - ctx_len: 0, - ngl: 0, - embedding: false, - n_parallel: 0, - }, + settings: {}, parameters: { - temperature: 0, - token_limit: 0, - top_k: 0, - top_p: 0, - stream: false, + stream: true, + max_tokens: 1024, }, engine: undefined, }, @@ -78,29 +84,20 @@ export const useCreateNewThread = () => { assistants: [assistantInfo], created: createdAt, updated: createdAt, - isFinishInit: false, } - // TODO: move isFinishInit here - const threadState: ThreadState = { - hasMore: false, - waitingForResponse: false, - lastMessage: undefined, - } - setThreadStates({ ...threadStates, [threadId]: threadState }) + setThreadModelRuntimeParams(thread.id, assistantInfo.model.parameters) + // add the new thread on top of the thread list to the state createNewThread(thread) setActiveThreadId(thread.id) } function updateThreadMetadata(thread: Thread) { - const updatedThread: Thread = { - ...thread, - } - updateThread(updatedThread) + updateThread(thread) extensionManager .get(ExtensionType.Conversational) - ?.saveThread(updatedThread) + ?.saveThread(thread) } return { diff --git a/web/hooks/useDeleteConversation.ts b/web/hooks/useDeleteThread.ts similarity index 62% rename from web/hooks/useDeleteConversation.ts rename to web/hooks/useDeleteThread.ts index b02796b10..8822b6aa8 100644 --- a/web/hooks/useDeleteConversation.ts +++ b/web/hooks/useDeleteThread.ts @@ -11,14 +11,15 @@ import { useActiveModel } from './useActiveModel' import { extensionManager } from '@/extension/ExtensionManager' import { - cleanConversationMessages, - deleteConversationMessage, + cleanChatMessageAtom as cleanChatMessagesAtom, + deleteChatMessageAtom as deleteChatMessagesAtom, getCurrentChatMessagesAtom, } from '@/helpers/atoms/ChatMessage.atom' import { threadsAtom, setActiveThreadIdAtom, -} from '@/helpers/atoms/Conversation.atom' + deleteThreadStateAtom, +} from '@/helpers/atoms/Thread.atom' export default function useDeleteThread() { const { activeModel } = useActiveModel() @@ -26,45 +27,51 @@ export default function useDeleteThread() { const setCurrentPrompt = useSetAtom(currentPromptAtom) const messages = useAtomValue(getCurrentChatMessagesAtom) - const setActiveConvoId = useSetAtom(setActiveThreadIdAtom) - const deleteMessages = useSetAtom(deleteConversationMessage) - const cleanMessages = useSetAtom(cleanConversationMessages) + const setActiveThreadId = useSetAtom(setActiveThreadIdAtom) + const deleteMessages = useSetAtom(deleteChatMessagesAtom) + const cleanMessages = useSetAtom(cleanChatMessagesAtom) + const deleteThreadState = useSetAtom(deleteThreadStateAtom) + + const cleanThread = async (threadId: string) => { + if (threadId) { + const thread = threads.filter((c) => c.id === threadId)[0] + cleanMessages(threadId) - const cleanThread = async (activeThreadId: string) => { - if (activeThreadId) { - const thread = threads.filter((c) => c.id === activeThreadId)[0] - cleanMessages(activeThreadId) if (thread) await extensionManager .get(ExtensionType.Conversational) ?.writeMessages( - activeThreadId, + threadId, messages.filter((msg) => msg.role === ChatCompletionRole.System) ) } } - const deleteThread = async (activeThreadId: string) => { - if (!activeThreadId) { + const deleteThread = async (threadId: string) => { + if (!threadId) { alert('No active thread') return } try { await extensionManager .get(ExtensionType.Conversational) - ?.deleteThread(activeThreadId) - const availableThreads = threads.filter((c) => c.id !== activeThreadId) + ?.deleteThread(threadId) + const availableThreads = threads.filter((c) => c.id !== threadId) setThreads(availableThreads) - deleteMessages(activeThreadId) + + // delete the thread state + deleteThreadState(threadId) + + deleteMessages(threadId) setCurrentPrompt('') toaster({ title: 'Thread successfully deleted.', description: `Thread with ${activeModel?.name} has been successfully deleted.`, }) if (availableThreads.length > 0) { - setActiveConvoId(availableThreads[0].id) + setActiveThreadId(availableThreads[0].id) } else { - setActiveConvoId(undefined) + setActiveThreadId(undefined) } } catch (err) { console.error(err) diff --git a/web/hooks/useGetAllThreads.ts b/web/hooks/useGetAllThreads.ts index 488e64f64..867434617 100644 --- a/web/hooks/useGetAllThreads.ts +++ b/web/hooks/useGetAllThreads.ts @@ -1,35 +1,50 @@ -import { ExtensionType, ThreadState } from '@janhq/core' +import { ExtensionType, ModelRuntimeParams, ThreadState } from '@janhq/core' import { ConversationalExtension } from '@janhq/core' import { useSetAtom } from 'jotai' import { extensionManager } from '@/extension/ExtensionManager' import { + threadModelRuntimeParamsAtom, threadStatesAtom, threadsAtom, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' const useGetAllThreads = () => { - const setConversationStates = useSetAtom(threadStatesAtom) - const setConversations = useSetAtom(threadsAtom) + const setThreadStates = useSetAtom(threadStatesAtom) + const setThreads = useSetAtom(threadsAtom) + const setThreadModelRuntimeParams = useSetAtom(threadModelRuntimeParamsAtom) const getAllThreads = async () => { try { - const threads = await extensionManager - .get(ExtensionType.Conversational) - ?.getThreads() + const threads = + (await extensionManager + .get(ExtensionType.Conversational) + ?.getThreads()) ?? [] + const threadStates: Record = {} - threads?.forEach((thread) => { + const threadModelParams: Record = {} + + threads.forEach((thread) => { if (thread.id != null) { const lastMessage = (thread.metadata?.lastMessage as string) ?? '' + threadStates[thread.id] = { hasMore: true, waitingForResponse: false, lastMessage, + isFinishInit: true, } + + // model params + const modelParams = thread.assistants?.[0]?.model?.parameters + threadModelParams[thread.id] = modelParams } }) - setConversationStates(threadStates) - setConversations(threads ?? []) + + // updating app states + setThreadStates(threadStates) + setThreads(threads) + setThreadModelRuntimeParams(threadModelParams) } catch (error) { console.error(error) } diff --git a/web/hooks/useGetConfiguredModels.ts b/web/hooks/useGetConfiguredModels.ts index 7c4d94edd..d79778a00 100644 --- a/web/hooks/useGetConfiguredModels.ts +++ b/web/hooks/useGetConfiguredModels.ts @@ -19,9 +19,6 @@ export function useGetConfiguredModels() { async function fetchModels() { setLoading(true) const models = await getConfiguredModels() - if (process.env.NODE_ENV === 'development') { - // models = [dummyModel, ...models] // TODO: NamH add back dummy model later - } setLoading(false) setModels(models) } diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 970aedbec..8913104d3 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -12,8 +12,9 @@ import { ThreadMessage, events, Model, + ConversationalExtension, + ModelRuntimeParams, } from '@janhq/core' -import { ConversationalExtension } from '@janhq/core' import { useAtom, useAtomValue, useSetAtom } from 'jotai' import { ulid } from 'ulid' @@ -32,9 +33,12 @@ import { } from '@/helpers/atoms/ChatMessage.atom' import { activeThreadAtom, + getActiveThreadModelRuntimeParamsAtom, + threadStatesAtom, updateThreadAtom, + updateThreadInitSuccessAtom, updateThreadWaitingForResponseAtom, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' export default function useSendChatMessage() { const activeThread = useAtomValue(activeThreadAtom) @@ -50,6 +54,10 @@ export default function useSendChatMessage() { const [queuedMessage, setQueuedMessage] = useState(false) const modelRef = useRef() + const threadStates = useAtomValue(threadStatesAtom) + const updateThreadInitSuccess = useSetAtom(updateThreadInitSuccessAtom) + const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) + useEffect(() => { modelRef.current = activeModel }, [activeModel]) @@ -109,7 +117,7 @@ export default function useSendChatMessage() { return new Promise((resolve) => { setTimeout(async () => { if (modelRef.current?.id !== modelId) { - console.log('waiting for model to start') + console.debug('waiting for model to start') await WaitForModelStarting(modelId) resolve() } else { @@ -127,8 +135,10 @@ export default function useSendChatMessage() { console.error('No active thread') return } + const activeThreadState = threadStates[activeThread.id] - if (!activeThread.isFinishInit) { + // if the thread is not initialized, we need to initialize it first + if (!activeThreadState.isFinishInit) { if (!selectedModel) { toaster({ title: 'Please select a model' }) return @@ -136,9 +146,14 @@ export default function useSendChatMessage() { const assistantId = activeThread.assistants[0].assistant_id ?? '' const assistantName = activeThread.assistants[0].assistant_name ?? '' const instructions = activeThread.assistants[0].instructions ?? '' + + const modelParams: ModelRuntimeParams = { + ...selectedModel.parameters, + ...activeModelParams, + } + const updatedThread: Thread = { ...activeThread, - isFinishInit: true, assistants: [ { assistant_id: assistantId, @@ -147,13 +162,13 @@ export default function useSendChatMessage() { model: { id: selectedModel.id, settings: selectedModel.settings, - parameters: selectedModel.parameters, + parameters: modelParams, engine: selectedModel.engine, }, }, ], } - + updateThreadInitSuccess(activeThread.id) updateThread(updatedThread) extensionManager @@ -191,11 +206,16 @@ export default function useSendChatMessage() { ]) ) const msgId = ulid() + + const modelRequest = selectedModel ?? activeThread.assistants[0].model const messageRequest: MessageRequest = { id: msgId, threadId: activeThread.id, messages, - model: selectedModel ?? activeThread.assistants[0].model, + model: { + ...modelRequest, + ...(activeModelParams ? { parameters: activeModelParams } : {}), + }, } const timestamp = Date.now() const threadMessage: ThreadMessage = { diff --git a/web/hooks/useSetActiveThread.ts b/web/hooks/useSetActiveThread.ts index a0d584116..0705901c3 100644 --- a/web/hooks/useSetActiveThread.ts +++ b/web/hooks/useSetActiveThread.ts @@ -9,7 +9,7 @@ import { setConvoMessagesAtom } from '@/helpers/atoms/ChatMessage.atom' import { getActiveThreadIdAtom, setActiveThreadIdAtom, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' export default function useSetActiveThread() { const activeThreadId = useAtomValue(getActiveThreadIdAtom) diff --git a/web/hooks/useUpdateModelParameters.ts b/web/hooks/useUpdateModelParameters.ts new file mode 100644 index 000000000..d6bb2d0db --- /dev/null +++ b/web/hooks/useUpdateModelParameters.ts @@ -0,0 +1,66 @@ +import { + ConversationalExtension, + ExtensionType, + ModelRuntimeParams, + Thread, +} from '@janhq/core' + +import { useAtomValue, useSetAtom } from 'jotai' + +import { extensionManager } from '@/extension' +import { + activeThreadStateAtom, + setThreadModelRuntimeParamsAtom, + threadsAtom, + updateThreadAtom, +} from '@/helpers/atoms/Thread.atom' + +export default function useUpdateModelParameters() { + const threads = useAtomValue(threadsAtom) + const updateThread = useSetAtom(updateThreadAtom) + const setThreadModelRuntimeParams = useSetAtom( + setThreadModelRuntimeParamsAtom + ) + const activeThreadState = useAtomValue(activeThreadStateAtom) + + const updateModelParameter = async ( + threadId: string, + params: ModelRuntimeParams + ) => { + const thread = threads.find((thread) => thread.id === threadId) + if (!thread) { + console.error(`Thread ${threadId} not found`) + return + } + + if (!activeThreadState) { + console.error('No active thread') + return + } + + // update the state + setThreadModelRuntimeParams(thread.id, params) + + if (!activeThreadState.isFinishInit) { + // if thread is not initialized, we don't need to update thread.json + return + } + + const assistants = thread.assistants.map((assistant) => { + assistant.model.parameters = params + return assistant + }) + + // update thread + const updatedThread: Thread = { + ...thread, + assistants, + } + updateThread(updatedThread) + extensionManager + .get(ExtensionType.Conversational) + ?.saveThread(updatedThread) + } + + return { updateModelParameter } +} diff --git a/web/package.json b/web/package.json index dd7faeb1d..15d2830b0 100644 --- a/web/package.json +++ b/web/package.json @@ -38,7 +38,6 @@ "sass": "^1.69.4", "tailwind-merge": "^2.0.0", "tailwindcss": "3.3.5", - "typescript": "5.2.2", "ulid": "^2.3.0", "uuid": "^9.0.1", "zod": "^3.22.4" diff --git a/web/screens/Chat/MessageToolbar/index.tsx b/web/screens/Chat/MessageToolbar/index.tsx index fe7cac1f5..7f8e5ca7e 100644 --- a/web/screens/Chat/MessageToolbar/index.tsx +++ b/web/screens/Chat/MessageToolbar/index.tsx @@ -21,7 +21,7 @@ import { deleteMessageAtom, getCurrentChatMessagesAtom, } from '@/helpers/atoms/ChatMessage.atom' -import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom' +import { activeThreadAtom } from '@/helpers/atoms/Thread.atom' const MessageToolbar = ({ message }: { message: ThreadMessage }) => { const deleteMessage = useSetAtom(deleteMessageAtom) diff --git a/web/screens/Chat/ModelSetting/index.tsx b/web/screens/Chat/ModelSetting/index.tsx new file mode 100644 index 000000000..e8c9b2453 --- /dev/null +++ b/web/screens/Chat/ModelSetting/index.tsx @@ -0,0 +1,60 @@ +import { useEffect, useState } from 'react' + +import { useForm } from 'react-hook-form' + +import { ModelRuntimeParams } from '@janhq/core' + +import { useAtomValue } from 'jotai' + +import { presetConfiguration } from './predefinedComponent' +import settingComponentBuilder, { + SettingComponentData, +} from './settingComponentBuilder' + +import { + getActiveThreadIdAtom, + getActiveThreadModelRuntimeParamsAtom, +} from '@/helpers/atoms/Thread.atom' + +export default function ModelSetting() { + const threadId = useAtomValue(getActiveThreadIdAtom) + const activeModelParams = useAtomValue(getActiveThreadModelRuntimeParamsAtom) + const [modelParams, setModelParams] = useState< + ModelRuntimeParams | undefined + >(activeModelParams) + + const { register } = useForm() + + useEffect(() => { + setModelParams(activeModelParams) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [threadId]) + + if (!modelParams) { + return
This thread has no model parameters
+ } + + const componentData: SettingComponentData[] = [] + Object.keys(modelParams).forEach((key) => { + const componentSetting = presetConfiguration[key] + + if (componentSetting) { + if ('value' in componentSetting.controllerData) { + componentSetting.controllerData.value = Number( + modelParams[key as keyof ModelRuntimeParams] + ) + } else if ('checked' in componentSetting.controllerData) { + componentSetting.controllerData.checked = modelParams[ + key as keyof ModelRuntimeParams + ] as boolean + } + componentData.push(componentSetting) + } + }) + + return ( +
+ {settingComponentBuilder(componentData, register)} +
+ ) +} diff --git a/web/screens/Chat/ModelSetting/predefinedComponent.ts b/web/screens/Chat/ModelSetting/predefinedComponent.ts new file mode 100644 index 000000000..d8299ae10 --- /dev/null +++ b/web/screens/Chat/ModelSetting/predefinedComponent.ts @@ -0,0 +1,59 @@ +import { SettingComponentData } from './settingComponentBuilder' + +export const presetConfiguration: Record = { + max_tokens: { + name: 'max_tokens', + title: 'Max Tokens', + description: 'Maximum context length the model can handle.', + controllerType: 'slider', + controllerData: { + min: 0, + max: 4096, + step: 128, + value: 2048, + }, + }, + ngl: { + name: 'ngl', + title: 'NGL', + description: 'Number of layers in the neural network.', + controllerType: 'slider', + controllerData: { + min: 1, + max: 100, + step: 1, + value: 100, + }, + }, + embedding: { + name: 'embedding', + title: 'Embedding', + description: 'Indicates if embedding layers are used.', + controllerType: 'checkbox', + controllerData: { + checked: true, + }, + }, + stream: { + name: 'stream', + title: 'Stream', + description: 'Stream', + controllerType: 'checkbox', + controllerData: { + checked: false, + }, + }, + temperature: { + name: 'temperature', + title: 'Temperature', + description: + "Controls randomness in model's responses. Higher values lead to more random responses.", + controllerType: 'slider', + controllerData: { + min: 0, + max: 2, + step: 0.1, + value: 0.7, + }, + }, +} diff --git a/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx b/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx new file mode 100644 index 000000000..604e70773 --- /dev/null +++ b/web/screens/Chat/ModelSetting/settingComponentBuilder.tsx @@ -0,0 +1,67 @@ +/* eslint-disable no-case-declarations */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import Checkbox from '@/containers/Checkbox' +import Slider from '@/containers/Slider' + +export type ControllerType = 'slider' | 'checkbox' + +export type SettingComponentData = { + name: string + title: string + description: string + controllerType: ControllerType + controllerData: SliderData | CheckboxData +} + +export type SliderData = { + min: number + max: number + step: number + value: number +} + +type CheckboxData = { + checked: boolean +} + +const settingComponentBuilder = ( + componentData: SettingComponentData[], + register: any +) => { + const components = componentData.map((data) => { + switch (data.controllerType) { + case 'slider': + const { min, max, step, value } = data.controllerData as SliderData + return ( + + ) + case 'checkbox': + const { checked } = data.controllerData as CheckboxData + return ( + + ) + default: + return null + } + }) + + return
{components}
+} + +export default settingComponentBuilder diff --git a/web/screens/Chat/Sidebar/index.tsx b/web/screens/Chat/Sidebar/index.tsx index cf8c46b48..7c3fc57db 100644 --- a/web/screens/Chat/Sidebar/index.tsx +++ b/web/screens/Chat/Sidebar/index.tsx @@ -16,7 +16,9 @@ import DropdownListSidebar, { import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import { activeThreadAtom } from '@/helpers/atoms/Conversation.atom' +import ModelSetting from '../ModelSetting' + +import { activeThreadAtom, threadStatesAtom } from '@/helpers/atoms/Thread.atom' export const showRightSideBarAtom = atom(true) @@ -25,10 +27,12 @@ export default function Sidebar() { const activeThread = useAtomValue(activeThreadAtom) const selectedModel = useAtomValue(selectedModelAtom) const { updateThreadMetadata } = useCreateNewThread() + const threadStates = useAtomValue(threadStatesAtom) const onReviewInFinderClick = async (type: string) => { if (!activeThread) return - if (!activeThread.isFinishInit) { + const activeThreadState = threadStates[activeThread.id] + if (!activeThreadState.isFinishInit) { alert('Thread is not started yet') return } @@ -60,7 +64,8 @@ export default function Sidebar() { const onViewJsonClick = async (type: string) => { if (!activeThread) return - if (!activeThread.isFinishInit) { + const activeThreadState = threadStates[activeThread.id] + if (!activeThreadState.isFinishInit) { alert('Thread is not started yet') return } @@ -189,6 +194,9 @@ export default function Sidebar() { >
+
+ +
diff --git a/web/screens/Chat/ThreadList/index.tsx b/web/screens/Chat/ThreadList/index.tsx index 8a4b85d17..5b5a8d91d 100644 --- a/web/screens/Chat/ThreadList/index.tsx +++ b/web/screens/Chat/ThreadList/index.tsx @@ -12,7 +12,7 @@ import { import { twMerge } from 'tailwind-merge' import { useCreateNewThread } from '@/hooks/useCreateNewThread' -import useDeleteThread from '@/hooks/useDeleteConversation' +import useDeleteThread from '@/hooks/useDeleteThread' import useGetAllThreads from '@/hooks/useGetAllThreads' import useGetAssistants from '@/hooks/useGetAssistants' @@ -25,7 +25,7 @@ import { activeThreadAtom, threadStatesAtom, threadsAtom, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' export default function ThreadList() { const threads = useAtomValue(threadsAtom) diff --git a/web/screens/Chat/index.tsx b/web/screens/Chat/index.tsx index 7053ae1a2..741fadbaf 100644 --- a/web/screens/Chat/index.tsx +++ b/web/screens/Chat/index.tsx @@ -29,9 +29,9 @@ import { activeThreadAtom, getActiveThreadIdAtom, waitingToSendMessage, -} from '@/helpers/atoms/Conversation.atom' +} from '@/helpers/atoms/Thread.atom' -import { activeThreadStateAtom } from '@/helpers/atoms/Conversation.atom' +import { activeThreadStateAtom } from '@/helpers/atoms/Thread.atom' const ChatScreen = () => { const activeThread = useAtomValue(activeThreadAtom) diff --git a/web/utils/conversation.ts b/web/utils/thread.ts similarity index 100% rename from web/utils/conversation.ts rename to web/utils/thread.ts From cafdaaaccdc302312697caf15e91a8e07a6d638f Mon Sep 17 00:00:00 2001 From: hiro Date: Sat, 9 Dec 2023 22:58:00 +0700 Subject: [PATCH 162/179] feat: Nitro sensing hardware init --- .../inference-nitro-extension/package.json | 4 +- .../src/@types/global.d.ts | 5 +++ .../inference-nitro-extension/src/module.ts | 40 +++++++++++++++---- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/extensions/inference-nitro-extension/package.json b/extensions/inference-nitro-extension/package.json index ef74fff08..ecbbf17a8 100644 --- a/extensions/inference-nitro-extension/package.json +++ b/extensions/inference-nitro-extension/package.json @@ -36,6 +36,7 @@ "kill-port": "^2.0.1", "path-browserify": "^1.0.1", "rxjs": "^7.8.1", + "systeminformation": "^5.21.20", "tcp-port-used": "^1.0.2", "ts-loader": "^9.5.0", "ulid": "^2.3.0" @@ -52,6 +53,7 @@ "tcp-port-used", "kill-port", "fetch-retry", - "electron-log" + "electron-log", + "systeminformation" ] } diff --git a/extensions/inference-nitro-extension/src/@types/global.d.ts b/extensions/inference-nitro-extension/src/@types/global.d.ts index 642f10909..f93a3e4c9 100644 --- a/extensions/inference-nitro-extension/src/@types/global.d.ts +++ b/extensions/inference-nitro-extension/src/@types/global.d.ts @@ -24,3 +24,8 @@ interface ModelOperationResponse { error?: any; modelFile?: string; } + +interface ResourcesInfo { + numCpuPhysicalCore: number; + memAvailable: number; +} \ No newline at end of file diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index d36553f40..64a7393fc 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -4,6 +4,7 @@ const path = require("path"); const { spawn } = require("child_process"); const tcpPortUsed = require("tcp-port-used"); const fetchRetry = require("fetch-retry")(global.fetch); +const si = require("systeminformation"); const log = require("electron-log"); @@ -167,7 +168,7 @@ async function checkAndUnloadNitro() { * Should run exactly platform specified Nitro binary version */ async function spawnNitroProcess(): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let binaryFolder = path.join(__dirname, "bin"); // Current directory by default let binaryName; @@ -190,10 +191,20 @@ async function spawnNitroProcess(): Promise { const binaryPath = path.join(binaryFolder, binaryName); + // Gather system information for CPU physical cores and memory + const nitroResourceProbe = await getResourcesInfo(); + console.log( + "Nitro with physical core: " + nitroResourceProbe.numCpuPhysicalCore + ); + // Execute the binary - subprocess = spawn(binaryPath, [1, "127.0.0.1", PORT], { - cwd: binaryFolder, - }); + subprocess = spawn( + binaryPath, + [nitroResourceProbe.numCpuPhysicalCore, "127.0.0.1", PORT], + { + cwd: binaryFolder, + } + ); // Handle subprocess output subprocess.stdout.on("data", (data) => { @@ -263,15 +274,28 @@ function validateModelVersion(): Promise { }); } -/** - * Cleans up any registered resources. - * Its module specific function, should be called when application is closed - */ + function dispose() { // clean other registered resources here killSubprocess(); } +/** + * Get the system resources information + */ +async function getResourcesInfo(): Promise { + return new Promise(async (resolve) => { + const cpu = await si.cpu(); + const mem = await si.mem(); + + const response = { + numCpuPhysicalCore: cpu.physicalCores, + memAvailable: mem.available, + }; + resolve(response); + }); +} + module.exports = { initModel, killSubprocess, From f528e9ea7739460ddecac357a9be7d0ec60ae9e4 Mon Sep 17 00:00:00 2001 From: hiro Date: Mon, 11 Dec 2023 21:22:56 +0700 Subject: [PATCH 163/179] fix: Update inference nitro with n_threads equals to physcial core num --- .../inference-nitro-extension/src/module.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index 64a7393fc..80d474b94 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -39,15 +39,21 @@ function stopModel(): Promise { * TODO: Should pass absolute of the model file instead of just the name - So we can modurize the module.ts to npm package * TODO: Should it be startModel instead? */ -function initModel(wrapper: any): Promise { +async function initModel(wrapper: any): Promise { currentModelFile = wrapper.modelFullPath; if (wrapper.model.engine !== "nitro") { return Promise.resolve({ error: "Not a nitro model" }); } else { - log.info("Started to load model " + wrapper.model.modelFullPath); + // Gather system information for CPU physical cores and memory + const nitroResourceProbe = await getResourcesInfo(); + console.log( + "Nitro with physical core: " + nitroResourceProbe.numCpuPhysicalCore + ); const settings = { llama_model_path: currentModelFile, ...wrapper.model.settings, + // This is critical and requires real system information + n_threads: nitroResourceProbe.numCpuPhysicalCore, }; log.info(`Load model settings: ${JSON.stringify(settings, null, 2)}`); return ( @@ -55,7 +61,7 @@ function initModel(wrapper: any): Promise { validateModelVersion() .then(checkAndUnloadNitro) // 2. Spawn the Nitro subprocess - .then(spawnNitroProcess) + .then(await spawnNitroProcess(nitroResourceProbe)) // 4. Load the model into the Nitro subprocess (HTTP POST request) .then(() => loadLLMModel(settings)) // 5. Check if the model is loaded successfully @@ -167,7 +173,7 @@ async function checkAndUnloadNitro() { * Using child-process to spawn the process * Should run exactly platform specified Nitro binary version */ -async function spawnNitroProcess(): Promise { +async function spawnNitroProcess(nitroResourceProbe: any): Promise { return new Promise(async (resolve, reject) => { let binaryFolder = path.join(__dirname, "bin"); // Current directory by default let binaryName; @@ -191,12 +197,6 @@ async function spawnNitroProcess(): Promise { const binaryPath = path.join(binaryFolder, binaryName); - // Gather system information for CPU physical cores and memory - const nitroResourceProbe = await getResourcesInfo(); - console.log( - "Nitro with physical core: " + nitroResourceProbe.numCpuPhysicalCore - ); - // Execute the binary subprocess = spawn( binaryPath, @@ -222,7 +222,7 @@ async function spawnNitroProcess(): Promise { reject(`Nitro process exited. ${code ?? ""}`); }); tcpPortUsed.waitUntilUsed(PORT, 300, 30000).then(() => { - resolve(); + resolve(nitroResourceProbe); }); }); } From 0e63689eae163cab4bb42c7490becace4462d229 Mon Sep 17 00:00:00 2001 From: hiro Date: Mon, 11 Dec 2023 21:37:23 +0700 Subject: [PATCH 164/179] fix: inference engine nitro stopModel undefined in module.ts --- extensions/inference-nitro-extension/src/module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index 80d474b94..b5ba69f5a 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -298,6 +298,7 @@ async function getResourcesInfo(): Promise { module.exports = { initModel, + stopModel, killSubprocess, dispose, }; From 16c66e968c08167e1891735fce39df9628a1ae94 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 05:58:08 +0700 Subject: [PATCH 165/179] chore: Update n_threads to cpu_threads --- core/src/types/index.ts | 1 + extensions/inference-nitro-extension/src/@types/global.d.ts | 1 + extensions/inference-nitro-extension/src/index.ts | 2 +- extensions/inference-nitro-extension/src/module.ts | 2 +- extensions/inference-openai-extension/src/index.ts | 1 - 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/types/index.ts b/core/src/types/index.ts index d5b51cfc0..81ea7e14e 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -275,6 +275,7 @@ export type ModelSettingParams = { ngl?: number; embedding?: boolean; n_parallel?: number; + cpu_threads: number; system_prompt?: string; user_prompt?: string; ai_prompt?: string; diff --git a/extensions/inference-nitro-extension/src/@types/global.d.ts b/extensions/inference-nitro-extension/src/@types/global.d.ts index f93a3e4c9..62eb65e52 100644 --- a/extensions/inference-nitro-extension/src/@types/global.d.ts +++ b/extensions/inference-nitro-extension/src/@types/global.d.ts @@ -12,6 +12,7 @@ declare const INFERENCE_URL: string; interface EngineSettings { ctx_len: number; ngl: number; + cpu_threads: number; cont_batching: boolean; embedding: boolean; } diff --git a/extensions/inference-nitro-extension/src/index.ts b/extensions/inference-nitro-extension/src/index.ts index e5f3f4360..f2fbf0d34 100644 --- a/extensions/inference-nitro-extension/src/index.ts +++ b/extensions/inference-nitro-extension/src/index.ts @@ -12,7 +12,6 @@ import { EventName, MessageRequest, MessageStatus, - ModelSettingParams, ExtensionType, ThreadContent, ThreadMessage, @@ -41,6 +40,7 @@ export default class JanInferenceNitroExtension implements InferenceExtension { private static _engineSettings: EngineSettings = { ctx_len: 2048, ngl: 100, + cpu_threads: 1, cont_batching: false, embedding: false, }; diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index b5ba69f5a..266566e91 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -53,7 +53,7 @@ async function initModel(wrapper: any): Promise { llama_model_path: currentModelFile, ...wrapper.model.settings, // This is critical and requires real system information - n_threads: nitroResourceProbe.numCpuPhysicalCore, + cpu_threads: nitroResourceProbe.numCpuPhysicalCore, }; log.info(`Load model settings: ${JSON.stringify(settings, null, 2)}`); return ( diff --git a/extensions/inference-openai-extension/src/index.ts b/extensions/inference-openai-extension/src/index.ts index 7e3e6e71e..6bab563dd 100644 --- a/extensions/inference-openai-extension/src/index.ts +++ b/extensions/inference-openai-extension/src/index.ts @@ -12,7 +12,6 @@ import { EventName, MessageRequest, MessageStatus, - ModelSettingParams, ExtensionType, ThreadContent, ThreadMessage, From 577921f21f56fbd5bd7c64b3d0ce68a2c4d5f357 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 06:12:29 +0700 Subject: [PATCH 166/179] chore: add cpu_threads to default model settings as 1 --- core/src/types/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/types/index.ts b/core/src/types/index.ts index 81ea7e14e..7314a4ae3 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -275,7 +275,7 @@ export type ModelSettingParams = { ngl?: number; embedding?: boolean; n_parallel?: number; - cpu_threads: number; + cpu_threads?: number; system_prompt?: string; user_prompt?: string; ai_prompt?: string; From 14f83ddb7078ca826130eea67b86504819a77232 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 07:27:25 +0700 Subject: [PATCH 167/179] fix: Revert drogon thread to 1 instead of CPU num --- .../inference-nitro-extension/src/module.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/extensions/inference-nitro-extension/src/module.ts b/extensions/inference-nitro-extension/src/module.ts index 266566e91..047581dbe 100644 --- a/extensions/inference-nitro-extension/src/module.ts +++ b/extensions/inference-nitro-extension/src/module.ts @@ -179,10 +179,8 @@ async function spawnNitroProcess(nitroResourceProbe: any): Promise { let binaryName; if (process.platform === "win32") { - // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries binaryName = "win-start.bat"; } else if (process.platform === "darwin") { - // Mac OS platform if (process.arch === "arm64") { binaryFolder = path.join(binaryFolder, "mac-arm64"); } else { @@ -190,21 +188,15 @@ async function spawnNitroProcess(nitroResourceProbe: any): Promise { } binaryName = "nitro"; } else { - // Linux - // Todo: Need to check for CUDA support to switch between CUDA and non-CUDA binaries - binaryName = "linux-start.sh"; // For other platforms + binaryName = "linux-start.sh"; } const binaryPath = path.join(binaryFolder, binaryName); // Execute the binary - subprocess = spawn( - binaryPath, - [nitroResourceProbe.numCpuPhysicalCore, "127.0.0.1", PORT], - { - cwd: binaryFolder, - } - ); + subprocess = spawn(binaryPath, [1, LOCAL_HOST, PORT], { + cwd: binaryFolder, + }); // Handle subprocess output subprocess.stdout.on("data", (data) => { @@ -274,7 +266,6 @@ function validateModelVersion(): Promise { }); } - function dispose() { // clean other registered resources here killSubprocess(); From 9ca65e189435e544ec18ea43dbe59b10c7da93bb Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:08:36 +0700 Subject: [PATCH 168/179] Fix update release url pipeline run failed (#947) * update-release-url pipeline add trigger commit to git if github event is release * Remove v in release tag * Change job name and add checkout step * Checkout step uses service account token --------- Co-authored-by: Hien To --- .github/workflows/jan-electron-build-nightly.yml | 8 ++++++++ .github/workflows/update-release-url.yml | 3 +++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/jan-electron-build-nightly.yml b/.github/workflows/jan-electron-build-nightly.yml index 366f63602..48f00898c 100644 --- a/.github/workflows/jan-electron-build-nightly.yml +++ b/.github/workflows/jan-electron-build-nightly.yml @@ -181,6 +181,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + fetch-depth: "0" + token: ${{ secrets.PAT_SERVICE_ACCOUNT }} + - name: Notify Discord uses: Ilshidur/action-discord@master with: @@ -207,6 +211,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + fetch-depth: "0" + token: ${{ secrets.PAT_SERVICE_ACCOUNT }} + - name: Notify Discord uses: Ilshidur/action-discord@master with: diff --git a/.github/workflows/update-release-url.yml b/.github/workflows/update-release-url.yml index 8b0be959d..a3ac5d275 100644 --- a/.github/workflows/update-release-url.yml +++ b/.github/workflows/update-release-url.yml @@ -14,6 +14,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 + with: + fetch-depth: "0" + token: ${{ secrets.PAT_SERVICE_ACCOUNT }} - name: Get Latest Release uses: pozetroninc/github-action-get-latest-release@v0.7.0 From c612096499b30abd735f82aa79dbe48335bc48cc Mon Sep 17 00:00:00 2001 From: Service Account Date: Tue, 12 Dec 2023 02:32:54 +0000 Subject: [PATCH 169/179] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index abc393850..e472d1133 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nighlty Build) - + Github action artifactory From db60bdfefaa30e4f09a1291dd787209b9907f838 Mon Sep 17 00:00:00 2001 From: Linh Tran Date: Tue, 12 Dec 2023 10:24:02 +0700 Subject: [PATCH 170/179] fix: windows bug - control buttons close,max,min hidden --- electron/managers/window.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electron/managers/window.ts b/electron/managers/window.ts index 0d5a0eaf4..4edf505b2 100644 --- a/electron/managers/window.ts +++ b/electron/managers/window.ts @@ -23,12 +23,12 @@ export class WindowManager { width: 1200, minWidth: 1200, height: 800, - show: false, + show: true, trafficLightPosition: { x: 10, y: 15, }, - titleBarStyle: 'hidden', + titleBarStyle: 'hiddenInset', vibrancy: 'sidebar', ...options, }) From c07582f2ceee95e6264195c82b3680394434e0fe Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:08:36 +0700 Subject: [PATCH 171/179] Update update-release-url.yml (#951) --- .github/workflows/update-release-url.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-release-url.yml b/.github/workflows/update-release-url.yml index a3ac5d275..828828d6b 100644 --- a/.github/workflows/update-release-url.yml +++ b/.github/workflows/update-release-url.yml @@ -13,10 +13,11 @@ jobs: environment: production steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: "0" token: ${{ secrets.PAT_SERVICE_ACCOUNT }} + ref: main - name: Get Latest Release uses: pozetroninc/github-action-get-latest-release@v0.7.0 From dc26a19c40b3605981486f3bad51207942a7f24d Mon Sep 17 00:00:00 2001 From: hiento09 <136591877+hiento09@users.noreply.github.com> Date: Tue, 12 Dec 2023 17:54:28 +0700 Subject: [PATCH 172/179] Upgrade electron from 26.2.1 to 28.0.0 (#958) --- electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/electron/package.json b/electron/package.json index 623071e38..793065230 100644 --- a/electron/package.json +++ b/electron/package.json @@ -86,7 +86,7 @@ "@types/pacote": "^11.1.7", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", - "electron": "26.2.1", + "electron": "28.0.0", "electron-builder": "^24.6.4", "electron-playwright-helpers": "^1.6.0", "eslint-plugin-react": "^7.33.2", From f5c111943a3894f40789f3bdf6ec042d850f397a Mon Sep 17 00:00:00 2001 From: Service Account Date: Tue, 12 Dec 2023 11:44:19 +0000 Subject: [PATCH 173/179] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e472d1133..068b117df 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nighlty Build) -
+ Github action artifactory From 8f5c5e1e4290a51798752e88a764810291ba5d80 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 19:41:48 +0700 Subject: [PATCH 174/179] chore: Bump nitro to 0.1.26 --- extensions/inference-nitro-extension/bin/version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/inference-nitro-extension/bin/version.txt b/extensions/inference-nitro-extension/bin/version.txt index 001d75287..7db267292 100644 --- a/extensions/inference-nitro-extension/bin/version.txt +++ b/extensions/inference-nitro-extension/bin/version.txt @@ -1 +1 @@ -0.1.23 +0.1.26 From 2d788571577da1724a468f4f7ff1b9ed27eef48d Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 11:30:02 +0700 Subject: [PATCH 175/179] chore: Add git ignore for newly generated nitro bundle artifacts --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d400a3b81..ba5983d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ core/lib/** # Nitro binary files extensions/inference-nitro-extension/bin/*/nitro +extensions/inference-nitro-extension/bin/*/*.metal extensions/inference-nitro-extension/bin/*/*.exe extensions/inference-nitro-extension/bin/*/*.dll -extensions/inference-nitro-extension/bin/*/*.metal \ No newline at end of file +extensions/inference-nitro-extension/bin/*/*.exp +extensions/inference-nitro-extension/bin/*/*.lib \ No newline at end of file From f2688771fff078c2e29a775784af645bf92cf613 Mon Sep 17 00:00:00 2001 From: hiro Date: Tue, 12 Dec 2023 08:54:43 +0700 Subject: [PATCH 176/179] feat: Add triton trtllm for engine for remote models --- core/src/types/index.ts | 2 +- .../README.md | 78 ++++++ .../package.json | 41 +++ .../src/@types/global.d.ts | 7 + .../src/helpers/sse.ts | 63 +++++ .../src/index.ts | 235 ++++++++++++++++++ .../tsconfig.json | 15 ++ .../webpack.config.js | 38 +++ 8 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 extensions/inference-triton-trtllm-extension/README.md create mode 100644 extensions/inference-triton-trtllm-extension/package.json create mode 100644 extensions/inference-triton-trtllm-extension/src/@types/global.d.ts create mode 100644 extensions/inference-triton-trtllm-extension/src/helpers/sse.ts create mode 100644 extensions/inference-triton-trtllm-extension/src/index.ts create mode 100644 extensions/inference-triton-trtllm-extension/tsconfig.json create mode 100644 extensions/inference-triton-trtllm-extension/webpack.config.js diff --git a/core/src/types/index.ts b/core/src/types/index.ts index 7314a4ae3..2e19f61d8 100644 --- a/core/src/types/index.ts +++ b/core/src/types/index.ts @@ -174,7 +174,7 @@ export type ThreadState = { enum InferenceEngine { nitro = "nitro", openai = "openai", - nvidia_triton = "nvidia_triton", + triton_trtllm = "triton_trtllm", hf_endpoint = "hf_endpoint", } diff --git a/extensions/inference-triton-trtllm-extension/README.md b/extensions/inference-triton-trtllm-extension/README.md new file mode 100644 index 000000000..455783efb --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/README.md @@ -0,0 +1,78 @@ +# Jan inference plugin + +Created using Jan app example + +# Create a Jan Plugin using Typescript + +Use this template to bootstrap the creation of a TypeScript Jan plugin. 🚀 + +## Create Your Own Plugin + +To create your own plugin, you can use this repository as a template! Just follow the below instructions: + +1. Click the Use this template button at the top of the repository +2. Select Create a new repository +3. Select an owner and name for your new repository +4. Click Create repository +5. Clone your new repository + +## Initial Setup + +After you've cloned the repository to your local machine or codespace, you'll need to perform some initial setup steps before you can develop your plugin. + +> [!NOTE] +> +> You'll need to have a reasonably modern version of +> [Node.js](https://nodejs.org) handy. If you are using a version manager like +> [`nodenv`](https://github.com/nodenv/nodenv) or +> [`nvm`](https://github.com/nvm-sh/nvm), you can run `nodenv install` in the +> root of your repository to install the version specified in +> [`package.json`](./package.json). Otherwise, 20.x or later should work! + +1. :hammer_and_wrench: Install the dependencies + + ```bash + npm install + ``` + +1. :building_construction: Package the TypeScript for distribution + + ```bash + npm run bundle + ``` + +1. :white_check_mark: Check your artifact + + There will be a tgz file in your plugin directory now + +## Update the Plugin Metadata + +The [`package.json`](package.json) file defines metadata about your plugin, such as +plugin name, main entry, description and version. + +When you copy this repository, update `package.json` with the name, description for your plugin. + +## Update the Plugin Code + +The [`src/`](./src/) directory is the heart of your plugin! This contains the +source code that will be run when your plugin extension functions are invoked. You can replace the +contents of this directory with your own code. + +There are a few things to keep in mind when writing your plugin code: + +- Most Jan Plugin Extension functions are processed asynchronously. + In `index.ts`, you will see that the extension function will return a `Promise`. + + ```typescript + import { core } from "@janhq/core"; + + function onStart(): Promise { + return core.invokePluginFunc(MODULE_PATH, "run", 0); + } + ``` + + For more information about the Jan Plugin Core module, see the + [documentation](https://github.com/janhq/jan/blob/main/core/README.md). + +So, what are you waiting for? Go ahead and start customizing your plugin! + diff --git a/extensions/inference-triton-trtllm-extension/package.json b/extensions/inference-triton-trtllm-extension/package.json new file mode 100644 index 000000000..862359fe6 --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/package.json @@ -0,0 +1,41 @@ +{ + "name": "@janhq/inference-triton-trt-llm-extension", + "version": "1.0.0", + "description": "Inference Engine for NVIDIA Triton with TensorRT-LLM Extension integration on Jan extension framework", + "main": "dist/index.js", + "module": "dist/module.js", + "author": "Jan ", + "license": "AGPL-3.0", + "scripts": { + "build": "tsc -b . && webpack --config webpack.config.js", + "build:publish": "rimraf *.tgz --glob && npm run build && npm pack && cpx *.tgz ../../electron/pre-install" + }, + "exports": { + ".": "./dist/index.js", + "./main": "./dist/module.js" + }, + "devDependencies": { + "cpx": "^1.5.0", + "rimraf": "^3.0.2", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" + }, + "dependencies": { + "@janhq/core": "file:../../core", + "fetch-retry": "^5.0.6", + "path-browserify": "^1.0.1", + "ts-loader": "^9.5.0", + "ulid": "^2.3.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "dist/*", + "package.json", + "README.md" + ], + "bundleDependencies": [ + "fetch-retry" + ] +} diff --git a/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts b/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts new file mode 100644 index 000000000..141284ad6 --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts @@ -0,0 +1,7 @@ +import { Model } from "@janhq/core"; + +declare const MODULE: string; + +declare interface EngineSettings { + base_url?: string; +} diff --git a/extensions/inference-triton-trtllm-extension/src/helpers/sse.ts b/extensions/inference-triton-trtllm-extension/src/helpers/sse.ts new file mode 100644 index 000000000..da20fa32d --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/src/helpers/sse.ts @@ -0,0 +1,63 @@ +import { Observable } from "rxjs"; +import { EngineSettings } from "../@types/global"; +import { Model } from "@janhq/core"; + +/** + * Sends a request to the inference server to generate a response based on the recent messages. + * @param recentMessages - An array of recent messages to use as context for the inference. + * @param engine - The engine settings to use for the inference. + * @param model - The model to use for the inference. + * @returns An Observable that emits the generated response as a string. + */ +export function requestInference( + recentMessages: any[], + engine: EngineSettings, + model: Model, + controller?: AbortController +): Observable { + return new Observable((subscriber) => { + const text_input = recentMessages.map((message) => message.text).join("\n"); + const requestBody = JSON.stringify({ + text_input: text_input, + max_tokens: 4096, + temperature: 0, + bad_words: "", + stop_words: "[DONE]", + stream: true + }); + fetch(`${engine.base_url}/v2/models/ensemble/generate_stream`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "text/event-stream", + "Access-Control-Allow-Origin": "*", + }, + body: requestBody, + signal: controller?.signal, + }) + .then(async (response) => { + const stream = response.body; + const decoder = new TextDecoder("utf-8"); + const reader = stream?.getReader(); + let content = ""; + + while (true && reader) { + const { done, value } = await reader.read(); + if (done) { + break; + } + const text = decoder.decode(value); + const lines = text.trim().split("\n"); + for (const line of lines) { + if (line.startsWith("data: ") && !line.includes("data: [DONE]")) { + const data = JSON.parse(line.replace("data: ", "")); + content += data.choices[0]?.delta?.content ?? ""; + subscriber.next(content); + } + } + } + subscriber.complete(); + }) + .catch((err) => subscriber.error(err)); + }); +} diff --git a/extensions/inference-triton-trtllm-extension/src/index.ts b/extensions/inference-triton-trtllm-extension/src/index.ts new file mode 100644 index 000000000..9e8d64bb2 --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/src/index.ts @@ -0,0 +1,235 @@ +/** + * @file This file exports a class that implements the InferenceExtension interface from the @janhq/core package. + * The class provides methods for initializing and stopping a model, and for making inference requests. + * It also subscribes to events emitted by the @janhq/core package and handles new message requests. + * @version 1.0.0 + * @module inference-nvidia-triton-trt-llm-extension/src/index + */ + +import { + ChatCompletionRole, + ContentType, + EventName, + MessageRequest, + MessageStatus, + ModelSettingParams, + ExtensionType, + ThreadContent, + ThreadMessage, + events, + fs, + Model, +} from "@janhq/core"; +import { InferenceExtension } from "@janhq/core"; +import { requestInference } from "./helpers/sse"; +import { ulid } from "ulid"; +import { join } from "path"; +import { EngineSettings } from "./@types/global"; + +/** + * A class that implements the InferenceExtension interface from the @janhq/core package. + * The class provides methods for initializing and stopping a model, and for making inference requests. + * It also subscribes to events emitted by the @janhq/core package and handles new message requests. + */ +export default class JanInferenceTritonTrtLLMExtension implements InferenceExtension { + private static readonly _homeDir = 'engines' + private static readonly _engineMetadataFileName = 'triton_trtllm.json' + + static _currentModel: Model; + + static _engineSettings: EngineSettings = { + "base_url": "", + }; + + controller = new AbortController(); + isCancelled = false; + + /** + * Returns the type of the extension. + * @returns {ExtensionType} The type of the extension. + */ + // TODO: To fix + type(): ExtensionType { + return undefined; + } + /** + * Subscribes to events emitted by the @janhq/core package. + */ + onLoad(): void { + fs.mkdir(JanInferenceTritonTrtLLMExtension._homeDir) + JanInferenceTritonTrtLLMExtension.writeDefaultEngineSettings() + + // Events subscription + events.on(EventName.OnMessageSent, (data) => + JanInferenceTritonTrtLLMExtension.handleMessageRequest(data, this) + ); + + events.on(EventName.OnModelInit, (model: Model) => { + JanInferenceTritonTrtLLMExtension.handleModelInit(model); + }); + + events.on(EventName.OnModelStop, (model: Model) => { + JanInferenceTritonTrtLLMExtension.handleModelStop(model); + }); + } + + /** + * Stops the model inference. + */ + onUnload(): void {} + + /** + * Initializes the model with the specified file name. + * @param {string} modelId - The ID of the model to initialize. + * @returns {Promise} A promise that resolves when the model is initialized. + */ + async initModel( + modelId: string, + settings?: ModelSettingParams + ): Promise { + return + } + + static async writeDefaultEngineSettings() { + try { + const engine_json = join(JanInferenceTritonTrtLLMExtension._homeDir, JanInferenceTritonTrtLLMExtension._engineMetadataFileName) + if (await fs.exists(engine_json)) { + JanInferenceTritonTrtLLMExtension._engineSettings = JSON.parse(await fs.readFile(engine_json)) + } + else { + await fs.writeFile(engine_json, JSON.stringify(JanInferenceTritonTrtLLMExtension._engineSettings, null, 2)) + } + } catch (err) { + console.error(err) + } + } + /** + * Stops the model. + * @returns {Promise} A promise that resolves when the model is stopped. + */ + async stopModel(): Promise {} + + /** + * Stops streaming inference. + * @returns {Promise} A promise that resolves when the streaming is stopped. + */ + async stopInference(): Promise { + this.isCancelled = true; + this.controller?.abort(); + } + + /** + * Makes a single response inference request. + * @param {MessageRequest} data - The data for the inference request. + * @returns {Promise} A promise that resolves with the inference response. + */ + async inference(data: MessageRequest): Promise { + const timestamp = Date.now(); + const message: ThreadMessage = { + thread_id: data.threadId, + created: timestamp, + updated: timestamp, + status: MessageStatus.Ready, + id: "", + role: ChatCompletionRole.Assistant, + object: "thread.message", + content: [], + }; + + return new Promise(async (resolve, reject) => { + requestInference(data.messages ?? [], + JanInferenceTritonTrtLLMExtension._engineSettings, + JanInferenceTritonTrtLLMExtension._currentModel) + .subscribe({ + next: (_content) => {}, + complete: async () => { + resolve(message); + }, + error: async (err) => { + reject(err); + }, + }); + }); + } + + private static async handleModelInit(model: Model) { + if (model.engine !== 'triton_trtllm') { return } + else { + JanInferenceTritonTrtLLMExtension._currentModel = model + JanInferenceTritonTrtLLMExtension.writeDefaultEngineSettings() + // Todo: Check model list with API key + events.emit(EventName.OnModelReady, model) + // events.emit(EventName.OnModelFail, model) + } + } + + private static async handleModelStop(model: Model) { + if (model.engine !== 'triton_trtllm') { return } + events.emit(EventName.OnModelStopped, model) + } + + /** + * Handles a new message request by making an inference request and emitting events. + * Function registered in event manager, should be static to avoid binding issues. + * Pass instance as a reference. + * @param {MessageRequest} data - The data for the new message request. + */ + private static async handleMessageRequest( + data: MessageRequest, + instance: JanInferenceTritonTrtLLMExtension + ) { + if (data.model.engine !== 'triton_trtllm') { return } + + const timestamp = Date.now(); + const message: ThreadMessage = { + id: ulid(), + thread_id: data.threadId, + assistant_id: data.assistantId, + role: ChatCompletionRole.Assistant, + content: [], + status: MessageStatus.Pending, + created: timestamp, + updated: timestamp, + object: "thread.message", + }; + events.emit(EventName.OnMessageResponse, message); + + instance.isCancelled = false; + instance.controller = new AbortController(); + + requestInference( + data?.messages ?? [], + this._engineSettings, + JanInferenceTritonTrtLLMExtension._currentModel, + instance.controller + ).subscribe({ + next: (content) => { + const messageContent: ThreadContent = { + type: ContentType.Text, + text: { + value: content.trim(), + annotations: [], + }, + }; + message.content = [messageContent]; + events.emit(EventName.OnMessageUpdate, message); + }, + complete: async () => { + message.status = MessageStatus.Ready; + events.emit(EventName.OnMessageUpdate, message); + }, + error: async (err) => { + const messageContent: ThreadContent = { + type: ContentType.Text, + text: { + value: "Error occurred: " + err.message, + annotations: [], + }, + }; + message.content = [messageContent]; + message.status = MessageStatus.Ready; + events.emit(EventName.OnMessageUpdate, message); + }, + }); + } +} diff --git a/extensions/inference-triton-trtllm-extension/tsconfig.json b/extensions/inference-triton-trtllm-extension/tsconfig.json new file mode 100644 index 000000000..b48175a16 --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "ES6", + "moduleResolution": "node", + + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": false, + "skipLibCheck": true, + "rootDir": "./src" + }, + "include": ["./src"] +} diff --git a/extensions/inference-triton-trtllm-extension/webpack.config.js b/extensions/inference-triton-trtllm-extension/webpack.config.js new file mode 100644 index 000000000..57a0adb0a --- /dev/null +++ b/extensions/inference-triton-trtllm-extension/webpack.config.js @@ -0,0 +1,38 @@ +const path = require("path"); +const webpack = require("webpack"); +const packageJson = require("./package.json"); + +module.exports = { + experiments: { outputModule: true }, + entry: "./src/index.ts", // Adjust the entry point to match your project's main file + mode: "production", + module: { + rules: [ + { + test: /\.tsx?$/, + use: "ts-loader", + exclude: /node_modules/, + }, + ], + }, + plugins: [ + new webpack.DefinePlugin({ + MODULE: JSON.stringify(`${packageJson.name}/${packageJson.module}`), + }), + ], + output: { + filename: "index.js", // Adjust the output file name as needed + path: path.resolve(__dirname, "dist"), + library: { type: "module" }, // Specify ESM output format + }, + resolve: { + extensions: [".ts", ".js"], + fallback: { + path: require.resolve("path-browserify"), + }, + }, + optimization: { + minimize: false, + }, + // Add loaders and other configuration as needed for your project +}; From 587f5addfa663e915ccb349b83079a79cdfa6474 Mon Sep 17 00:00:00 2001 From: hiro Date: Wed, 13 Dec 2023 01:27:18 +0700 Subject: [PATCH 177/179] fix: Fix issues based on Louis comments --- extensions/inference-triton-trtllm-extension/package.json | 3 ++- .../inference-triton-trtllm-extension/src/@types/global.d.ts | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/extensions/inference-triton-trtllm-extension/package.json b/extensions/inference-triton-trtllm-extension/package.json index 862359fe6..ff2d4cc8b 100644 --- a/extensions/inference-triton-trtllm-extension/package.json +++ b/extensions/inference-triton-trtllm-extension/package.json @@ -25,7 +25,8 @@ "fetch-retry": "^5.0.6", "path-browserify": "^1.0.1", "ts-loader": "^9.5.0", - "ulid": "^2.3.0" + "ulid": "^2.3.0", + "rxjs": "^7.8.1" }, "engines": { "node": ">=18.0.0" diff --git a/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts b/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts index 141284ad6..6224b8e68 100644 --- a/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts +++ b/extensions/inference-triton-trtllm-extension/src/@types/global.d.ts @@ -1,7 +1,5 @@ import { Model } from "@janhq/core"; -declare const MODULE: string; - declare interface EngineSettings { base_url?: string; } From cca58566e294bbaa121fac0b1a426bad6cbfeca0 Mon Sep 17 00:00:00 2001 From: Service Account Date: Tue, 12 Dec 2023 20:16:29 +0000 Subject: [PATCH 178/179] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 068b117df..44d3e6e7f 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nighlty Build) - + Github action artifactory From f9b105b8f58dd73ed3ecae531cc67125a37dffc6 Mon Sep 17 00:00:00 2001 From: Service Account Date: Wed, 13 Dec 2023 05:11:28 +0000 Subject: [PATCH 179/179] janhq/jan: Update README.md with nightly build artifact URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 44d3e6e7f..88895cb84 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Jan is an open-source ChatGPT alternative that runs 100% offline on your compute Experimental (Nighlty Build) - + Github action artifactory